feat: add redis db to persist images; bug fixes

This commit is contained in:
Travis Fischer
2022-03-23 05:18:09 -04:00
parent 5417bb9bbc
commit 40972ecca6
10 changed files with 166 additions and 76 deletions

View File

@@ -10,5 +10,12 @@
# Optional (for fathom analytics)
#NEXT_PUBLIC_FATHOM_ID=
# Optional (for rendering tweets efficiently)
TWITTER_ACCESS_TOKEN=
# Optional (for rendering tweets more efficiently)
#TWITTER_ACCESS_TOKEN=
# Optional (for persisting preview images to redis)
# NOTE: if you want to enable redis, only REDIS_HOST and REDIS_PASSWORD are required
#REDIS_HOST=
#REDIS_PASSWORD=
#REDIS_USER='default'
#REDIS_NAMESPACE='preview-images'

View File

@@ -18,7 +18,7 @@ import { NotionRenderer, Code, Collection, CollectionRow } from 'react-notion-x'
// utils
import { getBlockTitle } from 'notion-utils'
import { mapPageUrl, getCanonicalPageUrl } from 'lib/map-page-url'
import { mapNotionImageUrl } from 'lib/map-image-url'
import { mapImageUrl } from 'lib/map-image-url'
import { getPageDescription } from 'lib/get-page-description'
import { getPageTweet } from 'lib/get-page-tweet'
import { searchNotion } from 'lib/search-notion'
@@ -129,7 +129,7 @@ export const NotionPage: React.FC<types.PageProps> = ({
const showTableOfContents = !!isBlogPost
const minTableOfContentsItems = 3
const socialImage = mapNotionImageUrl(
const socialImage = mapImageUrl(
(block as PageBlock).format?.page_cover || config.defaultPageCover,
block
)
@@ -282,7 +282,7 @@ export const NotionPage: React.FC<types.PageProps> = ({
defaultPageCover={config.defaultPageCover}
defaultPageCoverPosition={config.defaultPageCoverPosition}
mapPageUrl={siteMapPageUrl}
mapImageUrl={mapNotionImageUrl}
mapImageUrl={mapImageUrl}
searchNotion={searchNotion}
pageAside={pageAside}
footer={

View File

@@ -73,9 +73,6 @@ export const defaultPageCoverPosition: number = getSiteConfig(
0.5
)
// Optional image CDN host to proxy all image requests through
export const imageCDNHost: string | null = getSiteConfig('imageCDNHost', null)
// Optional whether or not to enable support for LQIP preview images
export const isPreviewImageSupportEnabled: boolean = getSiteConfig(
'isPreviewImageSupportEnabled',
@@ -96,6 +93,23 @@ export const includeNotionIdInUrls: boolean = getSiteConfig(
// ----------------------------------------------------------------------------
// Optional redis instance for persisting preview images
// (if you want to enable redis, only REDIS_HOST and REDIS_PASSWORD are required)
// we recommend that you store these in a local `.env` file
export const redisHost: string | null = getEnv('REDIS_HOST', null)
export const redisPassword: string | null = getEnv('REDIS_PASSWORD', null)
export const redisUser: string = getEnv('REDIS_USER', 'default')
export const redisUrl = getEnv(
'REDIS_URL',
`redis://${redisUser}:${redisPassword}@${redisHost}`
)
export const redisNamespace: string | null = getEnv(
'REDIS_NAMESPACE',
'preview-images'
)
// ----------------------------------------------------------------------------
export const isServer = typeof window === 'undefined'
export const port = getEnv('PORT', '3000')

14
lib/db.ts Normal file
View File

@@ -0,0 +1,14 @@
import Keyv from 'keyv'
import {
isPreviewImageSupportEnabled,
redisUrl,
redisNamespace
} from './config'
let db: Keyv
if (isPreviewImageSupportEnabled) {
db = new Keyv(redisUrl, { namespace: redisNamespace || undefined })
}
export { db }

View File

@@ -1,55 +1,3 @@
import { Block } from 'notion-types'
import { imageCDNHost } from './config'
import { defaultMapImageUrl } from 'react-notion-x'
export const mapNotionImageUrl = (url: string, block: Block) => {
if (!url) {
return null
}
if (url.startsWith('data:')) {
return url
}
if (imageCDNHost && url.startsWith(imageCDNHost)) {
return url
}
// const origUrl = url
if (url.startsWith('/images')) {
url = `https://www.notion.so${url}`
}
// more recent versions of notion don't proxy unsplash images
if (!url.startsWith('https://images.unsplash.com')) {
url = `https://www.notion.so${
url.startsWith('/image') ? url : `/image/${encodeURIComponent(url)}`
}`
const notionImageUrlV2 = new URL(url)
let table = block.parent_table === 'space' ? 'block' : block.parent_table
if (table === 'collection') {
table = 'block'
}
notionImageUrlV2.searchParams.set('table', table)
notionImageUrlV2.searchParams.set('id', block.id)
notionImageUrlV2.searchParams.set('cache', 'v2')
url = notionImageUrlV2.toString()
}
// console.log({ url, origUrl })
return mapImageUrl(url)
}
export const mapImageUrl = (imageUrl: string) => {
if (imageUrl.startsWith('data:')) {
return imageUrl
}
if (imageCDNHost) {
return `${imageCDNHost}/${encodeURIComponent(imageUrl)}`
} else {
return imageUrl
}
}
export const mapImageUrl = defaultMapImageUrl

View File

@@ -2,9 +2,10 @@ import got from 'got'
import lqip from 'lqip-modern'
import pMap from 'p-map'
import pMemoize from 'p-memoize'
import { ExtendedRecordMap, PreviewImage, PreviewImageMap } from 'notion-types'
import { mapNotionImageUrl } from './map-image-url'
import { db } from './db'
import { mapImageUrl } from './map-image-url'
// NOTE: this is just an example of how to pre-compute preview images.
// Depending on how many images you're working with, this can potentially be
@@ -46,7 +47,7 @@ export async function getPreviewImageMap(
return null
})
.filter(Boolean)
.map(({ block, url }) => mapNotionImageUrl(url, block))
.map(({ block, url }) => mapImageUrl(url, block))
.filter(Boolean)
const urls = Array.from(new Set(imageUrls))
@@ -60,16 +61,26 @@ export async function getPreviewImageMap(
}
async function createPreviewImage(url: string): Promise<PreviewImage | null> {
const cacheKey = url
try {
const cachedPreviewImage = await db.get(cacheKey)
if (cachedPreviewImage) {
return cachedPreviewImage
}
const { body } = await got(url, { responseType: 'buffer' })
const result = await lqip(body)
console.log('lqip', result.metadata)
return {
const previewImage = {
originalWidth: result.metadata.originalWidth,
originalHeight: result.metadata.originalHeight,
dataURIBase64: result.metadata.dataURIBase64
}
await db.set(cacheKey, previewImage)
return previewImage
} catch (err) {
console.warn('error creating preview image', url, err)
return null

View File

@@ -25,11 +25,13 @@
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
},
"dependencies": {
"@keyv/redis": "^2.2.3",
"classnames": "^2.3.1",
"date-fns": "^2.25.0",
"fathom-client": "^3.0.0",
"got": "^11.8.2",
"isomorphic-unfetch": "^3.1.0",
"keyv": "^4.1.1",
"lqip-modern": "^1.2.0",
"next": "^12.1.0",
"node-fetch": "^2.6.1",
@@ -42,7 +44,7 @@
"react-body-classname": "^1.3.1",
"react-dom": "^17.0.2",
"react-icons": "^4.3.1",
"react-notion-x": "^4.19.2",
"react-notion-x": "^4.19.6",
"react-static-tweets": "^0.7.1",
"react-use": "^17.3.2",
"static-tweets": "^0.7.1",

View File

@@ -27,6 +27,11 @@ module.exports = {
defaultPageCover: null,
defaultPageCoverPosition: 0.5,
// whether or not to enable support for LQIP preview images (optional)
// NOTE: this requires you to set up an external key-value store and add the
// environment variables specified in .env.example
isPreviewImageSupportEnabled: true,
// map of notion page IDs to URL paths (optional)
// any pages defined here will override their default URL paths
// example:

View File

@@ -85,7 +85,6 @@
.notion-collection-card-cover {
border-radius: 16px;
overflow: visible;
box-shadow: 2px 2px 8px 4px rgba(15, 15, 15, 0.1);
}
@@ -179,14 +178,21 @@
padding: 0;
}
.notion-page-cover {
max-width: 1200px;
.notion-page-cover-wrapper,
.notion-page-cover-wrapper span,
.notion-page-cover-wrapper img {
max-width: 1200px !important;
border-radius: 24px;
}
.notion-page-cover-wrapper {
box-shadow: 2px 2px 8px 4px rgba(15, 15, 15, 0.1);
}
@media only screen and (max-width: 1200px) {
.notion-page-cover {
.notion-page-cover-wrapper,
.notion-page-cover-wrapper span,
.notion-page-cover-wrapper img {
border-radius: 0;
}
}

View File

@@ -38,6 +38,13 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@keyv/redis@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@keyv/redis/-/redis-2.2.3.tgz#af5b1ea32d847a63ce24012844af7323b3c421a7"
integrity sha512-d9Maf1LzT6Ti5hWsVzaWFriFmXrscK1eUl/etNquQgAJxH7Drecbn+uNZXMc6xb78Ju9szy0fD9RAp/G9RzAdg==
dependencies:
ioredis "^4.28.5"
"@mapbox/rehype-prism@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@mapbox/rehype-prism/-/rehype-prism-0.5.0.tgz#b756308ebf3af8f92a6359cd78010a7770453e85"
@@ -745,6 +752,11 @@ clone-response@^1.0.2:
dependencies:
mimic-response "^1.0.0"
cluster-key-slot@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
@@ -915,7 +927,7 @@ debug@^3.1.0:
dependencies:
ms "^2.1.1"
debug@^4.0.0:
debug@^4.0.0, debug@^4.3.1:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -982,6 +994,11 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
denque@^1.1.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
dequal@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
@@ -1873,6 +1890,23 @@ invariant@^2.2.4:
dependencies:
loose-envify "^1.0.0"
ioredis@^4.28.5:
version "4.28.5"
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.5.tgz#5c149e6a8d76a7f8fa8a504ffc85b7d5b6797f9f"
integrity sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==
dependencies:
cluster-key-slot "^1.1.0"
debug "^4.3.1"
denque "^1.1.0"
lodash.defaults "^4.2.0"
lodash.flatten "^4.4.0"
lodash.isarguments "^3.1.0"
p-map "^2.1.0"
redis-commands "1.7.0"
redis-errors "^1.2.0"
redis-parser "^3.0.0"
standard-as-callback "^2.1.0"
is-alphabetical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
@@ -2136,6 +2170,13 @@ keyv@^4.0.0:
dependencies:
json-buffer "3.0.1"
keyv@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.1.1.tgz#02c538bfdbd2a9308cc932d4096f05ae42bfa06a"
integrity sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==
dependencies:
json-buffer "3.0.1"
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@@ -2163,6 +2204,21 @@ loader-utils@^2.0.0:
emojis-list "^3.0.0"
json5 "^2.1.2"
lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
lodash.isarguments@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -2655,6 +2711,11 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
p-map@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
@@ -3054,10 +3115,10 @@ react-modal@^3.14.3:
react-lifecycles-compat "^3.0.0"
warning "^4.0.3"
react-notion-x@^4.19.2:
version "4.19.2"
resolved "https://registry.yarnpkg.com/react-notion-x/-/react-notion-x-4.19.2.tgz#8f3b06c87a425afa9c52a4b5ce84f9087898b775"
integrity sha512-bmK92R58z5iPHWaWxb+QbpyMxukOypNHH2DAK9cDpf+WePEXNnqpcPcp1B03Pi3cexlXn5nTBBfdoyAAJVsEkA==
react-notion-x@^4.19.6:
version "4.19.6"
resolved "https://registry.yarnpkg.com/react-notion-x/-/react-notion-x-4.19.6.tgz#7922f650e9113f1328557d7bb89151efc09a8e1e"
integrity sha512-JmUXnmCGAifAw4jC9zzQ0qquWgNWnGVLuG74j5fMD9bUdpFibS/LWq2v+irz2fBOWLXA/YX2qKrXarSs1msxww==
dependencies:
"@matejmazur/react-katex" "^3.1.3"
date-fns "^2.15.0"
@@ -3191,6 +3252,23 @@ readable-stream@^3.1.1, readable-stream@^3.4.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
redis-commands@1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==
redis-errors@^1.0.0, redis-errors@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
redis-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
dependencies:
redis-errors "^1.0.0"
refractor@^3.0.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.3.1.tgz#ebbc04b427ea81dc25ad333f7f67a0b5f4f0be3a"
@@ -3562,6 +3640,11 @@ stacktrace-js@^2.0.2:
stack-generator "^2.0.5"
stacktrace-gps "^3.0.4"
standard-as-callback@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
static-tweets@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/static-tweets/-/static-tweets-0.7.1.tgz#162258d172f67d9685c3738ab026ef731cdc51e7"