返回 AiToEarn
server-fetch.ts
根目录 / project / aitoearn-web / src / api / _server / server-fetch.ts
1 /**
2 * server-fetch - 服务端 fetch 封装
3 * 用于 Server Component / generateMetadata / sitemap 等 SSR 场景
4 * 无客户端依赖(不使用 useUserStore、directTrans)
5 */
6
7 interface ServerFetchOptions {
8 revalidate?: number | false
9 tags?: string[]
10 }
11
12 interface ApiResponse<T> {
13 code: number | string
14 data: T
15 message: string
16 }
17
18 function getBaseUrl() {
19 // 生产环境 NEXT_PUBLIC_API_URL 是 /api(相对路径),SSR 需要完整 URL
20 const apiUrl = process.env.NEXT_PUBLIC_API_URL || '/api'
21 if (apiUrl.startsWith('http')) {
22 return apiUrl
23 }
24 const hostUrl = process.env.NEXT_PUBLIC_HOST_URL || 'https://aitoearn.ai'
25 return `${hostUrl}${apiUrl}`
26 }
27
28 /**
29 * 服务端 GET 请求
30 * 利用 Next.js fetch + next.revalidate 实现 ISR 缓存
31 */
32 export async function serverFetch<T>(
33 path: string,
34 params?: Record<string, string | number | boolean | undefined>,
35 options: ServerFetchOptions = {},
36 ): Promise<ApiResponse<T> | null> {
37 try {
38 const baseUrl = getBaseUrl()
39 const url = new URL(`${baseUrl}/${path}`)
40
41 if (params) {
42 Object.entries(params).forEach(([key, value]) => {
43 if (value !== undefined) {
44 url.searchParams.set(key, String(value))
45 }
46 })
47 }
48
49 const fetchOptions: RequestInit & { next?: { revalidate?: number | false, tags?: string[] } } = {
50 method: 'GET',
51 headers: {
52 'Content-Type': 'application/json',
53 },
54 next: {},
55 }
56
57 if (options.revalidate !== undefined) {
58 fetchOptions.next!.revalidate = options.revalidate
59 }
60 if (options.tags) {
61 fetchOptions.next!.tags = options.tags
62 }
63
64 const res = await fetch(url.toString(), fetchOptions)
65 if (!res.ok)
66 return null
67
68 const data: ApiResponse<T> = await res.json()
69 return data
70 }
71 catch {
72 return null
73 }
74 }
75
75 lines TYPESCRIPT