Skip to content

Commit 6848154

Browse files
committed
feat: cache something
Signed-off-by: Innei <[email protected]>
1 parent 3833c80 commit 6848154

File tree

6 files changed

+136
-24
lines changed

6 files changed

+136
-24
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"@tanstack/react-query": "5.21.2",
6767
"@tanstack/react-query-devtools": "5.21.3",
6868
"@tanstack/react-query-persist-client": "5.21.2",
69+
"@upstash/redis": "1.28.4",
6970
"@vercel/analytics": "1.2.0",
7071
"@vercel/postgres": "0.7.2",
7172
"axios": "1.6.7",

pnpm-lock.yaml

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/(app)/(home)/layout.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,29 @@ import { dehydrate } from '@tanstack/react-query'
22
import type { PropsWithChildren } from 'react'
33

44
import { QueryHydrate } from '~/components/common/QueryHydrate'
5+
import { getOrSetCache } from '~/lib/cache'
56
import { isShallowEqualArray } from '~/lib/lodash'
67
import { getQueryClient } from '~/lib/query-client.server'
78
import { apiClient } from '~/lib/request'
89
import { requestErrorHandler } from '~/lib/request.server'
910

1011
import { queryKey } from './query'
1112

12-
export const revalidate = 60
13+
export const revalidate = 600
1314

1415
export default async function HomeLayout(props: PropsWithChildren) {
1516
const queryClient = getQueryClient()
1617
await queryClient
1718
.fetchQuery({
1819
queryKey,
1920
queryFn: async () => {
20-
return (await apiClient.aggregate.getTop(5)).$serialized
21+
return getOrSetCache(
22+
'aggregate-top',
23+
async () => {
24+
return (await apiClient.aggregate.getTop(5)).$serialized
25+
},
26+
revalidate,
27+
)
2128
},
2229
})
2330
.catch(requestErrorHandler)

src/app/(app)/feed/route.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { getQueryClient } from '~/lib/query-client.server'
2020
import { apiClient } from '~/lib/request'
2121

2222
export const dynamic = 'force-dynamic'
23-
export const revalidate = 60 * 10 // 10 min
23+
export const revalidate = 60 * 60 * 24 // 1 day
2424

2525
interface RSSProps {
2626
title: string
@@ -108,7 +108,11 @@ ${ReactDOM.renderToString(
108108
extendsRules: {
109109
codeBlock: {
110110
react(node, output, state) {
111-
if (node.lang === 'mermaid' || node.lang === 'excalidraw') {
111+
if (
112+
node.lang === 'mermaid' ||
113+
node.lang === 'excalidraw' ||
114+
node.lang === 'component'
115+
) {
112116
return <NotSupportRender />
113117
}
114118
return (
@@ -151,6 +155,9 @@ ${ReactDOM.renderToString(
151155
return new Response(xml, {
152156
headers: {
153157
'Content-Type': 'application/xml',
158+
'Cache-Control': 'max-age=60',
159+
'CDN-Cache-Control': 'max-age=86400',
160+
'Vercel-CDN-Cache-Control': 'max-age=86400',
154161
},
155162
})
156163
}

src/app/(app)/layout.tsx

+23-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Analytics } from '@vercel/analytics/react'
21
import { ToastContainer } from 'react-toastify'
3-
import type { AggregateRoot } from '@mx-space/api-client'
2+
import { unstable_cache } from 'next/cache'
43
import type { Viewport } from 'next'
54
import type { PropsWithChildren } from 'react'
65

@@ -15,6 +14,7 @@ import { AccentColorStyleInjector } from '~/components/modules/shared/AccentColo
1514
import { SearchPanelWithHotKey } from '~/components/modules/shared/SearchFAB'
1615
import { TocAutoScroll } from '~/components/modules/toc/TocAutoScroll'
1716
import { attachUAAndRealIp } from '~/lib/attach-ua'
17+
import { getOrSetCache } from '~/lib/cache'
1818
import { sansFont, serifFont } from '~/lib/fonts'
1919
import { getQueryClient } from '~/lib/query-client.server'
2020
import { AggregationProvider } from '~/providers/root/aggregation-data-provider'
@@ -26,9 +26,7 @@ import { Analyze } from './analyze'
2626

2727
const { version } = PKG
2828

29-
export const revalidate = 60
30-
31-
let aggregationData: (AggregateRoot & { theme: AppThemeConfig }) | null = null
29+
export const revalidate = 300 // 300s
3230

3331
export function generateViewport(): Viewport {
3432
return {
@@ -44,14 +42,27 @@ export function generateViewport(): Viewport {
4442
}
4543
}
4644

47-
export const generateMetadata = async () => {
48-
const queryClient = getQueryClient()
45+
const key = 'root-data'
46+
const fetchAggregationData = unstable_cache(
47+
async () => {
48+
const queryClient = getQueryClient()
4949

50-
const fetchedData =
51-
aggregationData ??
52-
(await queryClient.fetchQuery(queries.aggregation.root()))
50+
return getOrSetCache(
51+
key,
52+
async () => {
53+
attachUAAndRealIp()
54+
55+
return queryClient.fetchQuery(queries.aggregation.root())
56+
},
57+
revalidate,
58+
)
59+
},
60+
[key],
61+
{ revalidate },
62+
)
63+
export const generateMetadata = async () => {
64+
const fetchedData = await fetchAggregationData()
5365

54-
aggregationData = fetchedData
5566
const {
5667
seo,
5768
url,
@@ -124,19 +135,12 @@ export const generateMetadata = async () => {
124135
}
125136

126137
export default async function RootLayout(props: PropsWithChildren) {
127-
attachUAAndRealIp()
128138
const { children } = props
129139

130-
const queryClient = getQueryClient()
131-
132-
const data = await queryClient.fetchQuery({
133-
...queries.aggregation.root(),
134-
})
140+
const data = await fetchAggregationData()
135141

136142
const themeConfig = data.theme
137143

138-
aggregationData = data
139-
140144
return (
141145
<ClerkProvider>
142146
<AppFeatureProvider tmdb={!!process.env.TMDB_API_KEY}>
@@ -185,7 +189,6 @@ export default async function RootLayout(props: PropsWithChildren) {
185189
<ScrollTop />
186190
</body>
187191
</html>
188-
<Analytics />
189192
</AppFeatureProvider>
190193
</ClerkProvider>
191194
)

src/lib/cache.ts

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Redis } from '@upstash/redis'
2+
3+
import { safeJsonParse } from './helper'
4+
5+
const UPSTASH_TOKEN = process.env.UPSTASH_TOKEN
6+
const UPSTASH_URL = process.env.UPSTASH_URL
7+
8+
let redis: Redis
9+
const getRedis = () => {
10+
if (redis) {
11+
return redis
12+
}
13+
if (!UPSTASH_TOKEN || !UPSTASH_URL) {
14+
return null
15+
}
16+
const _redis = new Redis({
17+
url: UPSTASH_URL,
18+
token: UPSTASH_TOKEN,
19+
})
20+
21+
redis = _redis
22+
return _redis
23+
}
24+
25+
export const setCache = async (key: string, value: string, ttl: number) => {
26+
const _redis = getRedis()
27+
if (!_redis) {
28+
return
29+
}
30+
await _redis
31+
.set(key, value, {
32+
ex: ttl,
33+
})
34+
.catch((err) => {
35+
console.error('setCache', err)
36+
})
37+
}
38+
39+
export const getCache = async (key: string) => {
40+
const _redis = getRedis()
41+
if (!_redis) {
42+
return null
43+
}
44+
return await _redis.get(key).catch((err) => {
45+
console.error('getCache', err)
46+
47+
return null
48+
})
49+
}
50+
51+
export const getOrSetCache = async <T>(
52+
key: string,
53+
setFn: () => Promise<T>,
54+
ttl: number,
55+
): Promise<T> => {
56+
const cache = await getCache(key)
57+
58+
if (cache) {
59+
if (typeof cache === 'string') {
60+
const tryParse = safeJsonParse(cache as any)
61+
if (tryParse) {
62+
return tryParse as T
63+
}
64+
} else {
65+
return cache as T
66+
}
67+
}
68+
69+
const fallbackData = await setFn()
70+
71+
await setCache(key, JSON.stringify(fallbackData), ttl)
72+
return fallbackData
73+
}
74+
75+
export const onlyGetOrSetCacheInVercelButFallback: typeof getOrSetCache =
76+
async (key, setFn, ttl) => {
77+
if (process.env.VERCEL || process.env.VERCEL_ENV) {
78+
return getOrSetCache(key, setFn, ttl)
79+
}
80+
return setFn()
81+
}

0 commit comments

Comments
 (0)