Skip to content

Commit

Permalink
feat: cache more
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Feb 29, 2024
1 parent dcba4ec commit 0fe5b44
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 25 deletions.
8 changes: 7 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ let nextConfig = {
}
},

webpack: (config, options) => {
webpack: (config, { webpack }) => {
const __dirname = new URL('./', import.meta.url).pathname
config.resolve.alias['jotai'] = path.resolve(
__dirname,
Expand All @@ -60,6 +60,12 @@ let nextConfig = {
bufferutil: 'commonjs bufferutil',
})

// config.plugins.push(
// new webpack.optimize.MinChunkSizePlugin({
// minChunkSize: 1024 * 100, // Minimum number of characters
// }),
// )

// if (
// process.env.SENTRY === 'true' &&
// process.env.NEXT_PUBLIC_SENTRY_DSN &&
Expand Down
4 changes: 4 additions & 0 deletions src/app.static.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export const appStaticConfig = {
providers: ['xlog'],
},
},

cache: {
enabled: true,
},
}

export const CDN_HOST = 'cdn.innei.ren'
Expand Down
5 changes: 3 additions & 2 deletions src/app/(app)/(home)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { dehydrate } from '@tanstack/react-query'
import type { PropsWithChildren } from 'react'

import { QueryHydrate } from '~/components/common/QueryHydrate'
import { CacheKeyMap } from '~/constants/keys'
import { onlyGetOrSetCacheInVercelButFallback } from '~/lib/cache'
import { isShallowEqualArray } from '~/lib/lodash'
import { getQueryClient } from '~/lib/query-client.server'
Expand All @@ -10,7 +11,7 @@ import { requestErrorHandler } from '~/lib/request.server'

import { queryKey } from './query'

export const revalidate = 600
export const revalidate = 3600

export default async function HomeLayout(props: PropsWithChildren) {
const queryClient = getQueryClient()
Expand All @@ -19,7 +20,7 @@ export default async function HomeLayout(props: PropsWithChildren) {
queryKey,
queryFn: async () => {
return onlyGetOrSetCacheInVercelButFallback(
'aggregate-top',
CacheKeyMap.AggregateTop,
async () => {
return (await apiClient.aggregate.getTop(5)).$serialized
},
Expand Down
15 changes: 12 additions & 3 deletions src/app/(app)/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,18 @@ export default ({ error, reset }: any) => {
return (
<NormalContainer>
<div className="flex min-h-[calc(100vh-10rem)] flex-col center">
<h2 className="mb-5">Something went wrong!</h2>
<StyledButton variant="primary" onClick={reset}>
Try again
<h2 className="mb-5">
<p>
服务端渲染页面时出现了错误,可能是 Next.js 服务访问 API
超时。请刷新重试。
</p>
<p>
多次出现错误请联系开发者 <a href="mailto:i@innei.in">Innei</a>
,谢谢!
</p>
</h2>
<StyledButton variant="primary" onClick={() => location.reload()}>
刷新
</StyledButton>
</div>
</NormalContainer>
Expand Down
5 changes: 3 additions & 2 deletions src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Root } from '~/components/layout/root/Root'
import { AccentColorStyleInjector } from '~/components/modules/shared/AccentColorStyleInjector'
import { SearchPanelWithHotKey } from '~/components/modules/shared/SearchFAB'
import { TocAutoScroll } from '~/components/modules/toc/TocAutoScroll'
import { CacheKeyMap } from '~/constants/keys'
import { attachUAAndRealIp } from '~/lib/attach-ua'
import { onlyGetOrSetCacheInVercelButFallback } from '~/lib/cache'
import { sansFont, serifFont } from '~/lib/fonts'
Expand All @@ -26,7 +27,7 @@ import { Analyze } from './analyze'

const { version } = PKG

export const revalidate = 300 // 300s
export const revalidate = 3600 // 3600s

export function generateViewport(): Viewport {
return {
Expand All @@ -42,7 +43,7 @@ export function generateViewport(): Viewport {
}
}

const key = 'root-data'
const key = CacheKeyMap.RootData
const fetchAggregationData = cache(async () => {
const queryClient = getQueryClient()

Expand Down
2 changes: 0 additions & 2 deletions src/app/(app)/notes/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import { useEffect } from 'react'
import { useParams } from 'next/navigation'

// import { captureException } from '@sentry/nextjs'

import { NotFound404 } from '~/components/common/404'
import { NotePasswordForm } from '~/components/modules/note/NotePasswordForm'
import { isRequestError, pickStatusCode } from '~/lib/is-error'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { cache } from 'react'
import type { Metadata } from 'next'

import { buildRoomName, RoomProvider } from '~/components/modules/activity'
Expand All @@ -18,14 +18,14 @@ import { queries } from '~/queries/definition'

import PostPage from './pageImpl'

const getData = async (params: PageParams) => {
const getData = cache(async (params: PageParams) => {
const { category, slug } = params
attachUAAndRealIp()
const data = await getQueryClient()
.fetchQuery(queries.post.bySlug(category, slug))
.catch(requestErrorHandler)
return data
}
})
export const generateMetadata = async ({
params,
}: {
Expand Down
20 changes: 13 additions & 7 deletions src/app/(app)/posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { PostPagination } from '~/components/modules/post/PostPagination'
import { NothingFound } from '~/components/modules/shared/NothingFound'
import { SearchFAB } from '~/components/modules/shared/SearchFAB'
import { BottomToUpTransitionView } from '~/components/ui/transition/BottomToUpTransitionView'
import { CacheKeyMap } from '~/constants/keys'
import { onlyGetOrSetCacheInVercelButFallback } from '~/lib/cache'
import { apiClient } from '~/lib/request'

interface Props {
Expand All @@ -23,14 +25,18 @@ export const metadata = {

export default async (props: Props) => {
const { page, size, orderBy, sortBy } = props?.searchParams || {}
const nextPage = page ? parseInt(page) : 1
const nextSize = size ? parseInt(size) : 10
const currentPage = page ? parseInt(page) : 1
const currentSize = size ? parseInt(size) : 10

const { $serialized } = await apiClient.post.getList(nextPage, nextSize, {
sortBy: sortBy as any,
sortOrder: orderBy === 'desc' ? -1 : 1,
})
const { data, pagination } = $serialized
const { data, pagination } = await onlyGetOrSetCacheInVercelButFallback(
CacheKeyMap.PostListWithPage(currentPage),
async () =>
await apiClient.post.getList(currentPage, currentSize, {
sortBy: sortBy as any,
sortOrder: orderBy === 'desc' ? -1 : 1,
}),
3600,
)

if (!data?.length) {
return <NothingFound />
Expand Down
5 changes: 2 additions & 3 deletions src/app/api/bilibili/check_live/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ export const revalidate = 10

export const GET = async (req: NextRequest): Promise<Response> => {
const liveId = req.nextUrl.searchParams.get('liveId')
const response = new NextServerResponse()
if (!liveId) {
return new NextServerResponse().status(400).end()
return response.status(400).end()
}
const queryClient = getQueryClient()
const res = await queryClient.fetchQuery({
Expand All @@ -38,8 +39,6 @@ export const GET = async (req: NextRequest): Promise<Response> => {
},
})

const response = new NextServerResponse()

if (!res?.data) {
return response.end()
}
Expand Down
19 changes: 18 additions & 1 deletion src/app/api/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
readDataFromRequest,
} from '@mx-space/webhook'

import { CacheKeyMap } from '~/constants/keys'
import { invalidateCache, invalidateCacheWithPrefix } from '~/lib/cache'
import { NextServerResponse } from '~/lib/edge-function.server'

// export const runtime = 'edge'
export const POST = async (nextreq: NextRequest) => {
const secret = process.env.WEBHOOK_SECRET
const res = new NextServerResponse()
Expand Down Expand Up @@ -37,6 +38,22 @@ export const POST = async (nextreq: NextRequest) => {
case BusinessEvents.NOTE_CREATE:
case BusinessEvents.NOTE_DELETE:
case BusinessEvents.NOTE_UPDATE: {
await Promise.all([invalidateCache(CacheKeyMap.AggregateTop)])
return res.status(200).send('OK')
}
case BusinessEvents.POST_CREATE:
case BusinessEvents.POST_UPDATE:
case BusinessEvents.POST_DELETE: {
await Promise.all([
invalidateCacheWithPrefix(CacheKeyMap.PostList),
invalidateCache(CacheKeyMap.AggregateTop),
])
return res.status(200).send('OK')
}
case BusinessEvents.PAGE_CREATE:
case BusinessEvents.PAGE_UPDATE:
case BusinessEvents.SAY_CREATE: {
await Promise.all([invalidateCache(CacheKeyMap.AggregateTop)])
return res.status(200).send('OK')
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/modules/post/PostItemHoverOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const PostItemHoverOverlay = () => {
layoutId="post-item-hover-overlay"
className={clsx(
'absolute z-[-1] rounded-xl',
'bg-slate-300/50 dark:bg-neutral-800',
'bg-accent/10 dark:bg-neutral-800',
'bottom-[1rem] left-[-1rem] right-[-1.5rem] top-[1rem]',
)}
/>
Expand Down
8 changes: 8 additions & 0 deletions src/constants/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ export const enum EmitKeyMap {
SocketConnected = 'SocketConnected',
SocketDisconnected = 'SocketDisconnected',
}

export const CacheKeyMap = {
RootData: 'root-data',
AggregateTop: 'aggregate-top',
PostListWithPage: (current: number) => CacheKeyMap.PostList + current,
PostList: 'post-list:',
Post: (id: string) => `post-${id}`,
}
39 changes: 39 additions & 0 deletions src/lib/cache.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Redis } from '@upstash/redis'

import { appStaticConfig } from '~/app.static.config'

import { safeJsonParse } from './helper'

const UPSTASH_TOKEN = process.env.UPSTASH_TOKEN
const UPSTASH_URL = process.env.UPSTASH_URL

let redis: Redis
const getRedis = () => {
if (!appStaticConfig.cache.enabled) {
return null
}
if (redis) {
return redis
}
Expand All @@ -27,6 +32,7 @@ export const setCache = async (key: string, value: string, ttl: number) => {
if (!_redis) {
return
}

await _redis
.set(key, value, {
ex: ttl,
Expand Down Expand Up @@ -67,6 +73,13 @@ export const getOrSetCache = async <T>(
}

const fallbackData = await setFn()
try {
if (typeof fallbackData === 'object' && fallbackData) {
Reflect.defineProperty(fallbackData, '__$cachedAt', {
value: Date.now(),
})
}
} catch {}

await setCache(key, JSON.stringify(fallbackData), ttl)
return fallbackData
Expand All @@ -79,3 +92,29 @@ export const onlyGetOrSetCacheInVercelButFallback: typeof getOrSetCache =
}
return setFn()
}

export const invalidateCache = (key: string) => {
const _redis = getRedis()
if (!_redis) {
return
}
return _redis.del(key).catch((err: any) => {
console.error(`invalidateCache error, key: ${key}. `, err)
})
}

export const invalidateCacheWithPrefix = async (prefix: string) => {
const _redis = getRedis()
if (!_redis) {
return
}
const keys = await _redis.keys(`${prefix}*`)

if (keys.length < 1) {
return
}

_redis.del(...keys).catch((err: any) => {
console.error(`invalidateCacheWithPrefix error, prefix: ${prefix}. `, err)
})
}

0 comments on commit 0fe5b44

Please sign in to comment.