Next.js 13+ App目录:服务端数据全局共享至客户端组件方案
问题
我正在用Next.js的/app目录开发应用,需要从API获取翻译JSON数据。服务端可以通过await调用API,还能以“hook”形式全局传递数据,这部分运行正常。
但我尝试在客户端组件里用Context实现类似功能时,页面加载和翻译加载之间有延迟,而且没法像服务端那样轻松缓存数据。
能不能在服务端(比如layout.tsx或者其他位置)获取这些数据,让客户端组件全局访问?就像之前的getInitialProps/getServerSideProps那样?
客户端Context实现代码
'use client'; import React, { createContext, useCallback, useEffect, useState } from 'react'; import { getActiveBrand } from '../actions/actions'; import { defaultLocale } from './translationProvider'; interface TranslationContextProps { translateProvider: ( locale?: string ) => (path: string, templates?: { [key: string]: string }) => string; } const TranslationContext = createContext<TranslationContextProps>( {} as TranslationContextProps ); // ts hack :/ interface AuthProviderProps { children: React.ReactNode | React.ReactNode[]; } const TranslationProvider = ({ children }: AuthProviderProps) => { const [translations, setTranslations] = useState( null as { [key: string]: any } | null ); const translateProvider = useCallback( (locale?: string) => { const lang = locale || defaultLocale; const translation = translations ? translations[lang] : {}; const translate = ( path: string, templates?: { [key: string]: string } ) => { if (translation !== null) { const keys = path.split('.'); let value: string; try { value = keys.reduce((a, c) => a[c], translation); if (templates && typeof value === 'string') Object.keys(templates).forEach(key => { value = value.replace(`{${key}}`, `${templates[key]}`); }); } catch (e: any) { return path; } return value; } return path; }; return translate; }, [translations] ); const getTranslations = async () => { const brand = await getActiveBrand(); const languages = {} as { [key: string]: any }; brand.data.languages.forEach(lang => { languages[lang.language] = JSON.parse(JSON.parse(lang.languageData)); }); setTranslations(languages); }; useEffect(() => { getTranslations(); }, []); return ( <TranslationContext.Provider value={{ translateProvider, }} > {children} </TranslationContext.Provider> ); }; export { TranslationProvider, TranslationContext };
服务端翻译实现代码
import { getActiveBrand } from '../actions/actions'; export const availableLanguages = ['en', 'de', 'cs', 'sk', 'sl', 'hu']; export const defaultLocale = 'de'; export async function translationProvider(lang: string) { const brand = await getActiveBrand(); const languageRemote = brand.data.languages.find( l => l.language === lang )?.languageData; const translation = languageRemote ? JSON.parse(JSON.parse(languageRemote)) : null; const t = (path: string, templates?: { [key: string]: string }): string => { if (translation !== null) { const keys = path.split('.'); let value: string; try { value = keys.reduce((a, c) => a[c], translation); if (templates && typeof value === 'string') Object.keys(templates).forEach(key => { value = value.replace(`{${key}}`, `${templates[key]}`); }); } catch (e: any) { return path; } return value; } return path; }; return t; }
解决方案
完全可以在服务端预获取翻译数据,通过服务端组件+客户端Context的方式传递给客户端组件,既消除加载延迟,又能利用Next.js的服务端缓存机制。
步骤1:改造客户端TranslationProvider
让组件接收服务端传入的翻译数据,移除客户端侧的API请求逻辑:
'use client'; import React, { createContext, useCallback } from 'react'; import { defaultLocale } from './translationProvider'; interface TranslationContextProps { translateProvider: ( locale?: string ) => (path: string, templates?: { [key: string]: string }) => string; } const TranslationContext = createContext<TranslationContextProps>( {} as TranslationContextProps ); interface TranslationProviderProps { children: React.ReactNode | React.ReactNode[]; translations: { [key: string]: any }; // 接收服务端预获取的翻译数据 } const TranslationProvider = ({ children, translations }: TranslationProviderProps) => { const translateProvider = useCallback( (locale?: string) => { const lang = locale || defaultLocale; const translation = translations[lang] || {}; const translate = ( path: string, templates?: { [key: string]: string } ) => { if (translation) { const keys = path.split('.'); let value: string; try { value = keys.reduce((a, c) => a[c], translation); if (templates && typeof value === 'string') Object.keys(templates).forEach(key => { value = value.replace(`{${key}}`, `${templates[key]}`); }); } catch (e: any) { return path; } return value; } return path; }; return translate; }, [translations] ); return ( <TranslationContext.Provider value={{ translateProvider, }} > {children} </TranslationContext.Provider> ); }; export { TranslationProvider, TranslationContext };
步骤2:在服务端根Layout中获取数据并传递
在app/layout.tsx(默认是服务端组件)中调用API获取翻译数据,传入客户端的TranslationProvider:
import { TranslationProvider } from './components/TranslationProvider'; import { getActiveBrand } from './actions/actions'; export default async function RootLayout({ children, }: { children: React.ReactNode; }) { // 服务端预获取翻译数据 const brand = await getActiveBrand(); const translations = {} as { [key: string]: any }; brand.data.languages.forEach(lang => { translations[lang.language] = JSON.parse(JSON.parse(lang.languageData)); }); return ( <html lang="de"> <body> <TranslationProvider translations={translations}> {children} </TranslationProvider> </body> </html> ); }
步骤3:客户端组件中使用Context
和之前逻辑一致,通过useContext直接获取翻译函数:
'use client'; import { useContext } from 'react'; import { TranslationContext } from './TranslationProvider'; export default function ClientComponent() { const { translateProvider } = useContext(TranslationContext); const t = translateProvider('de'); return <div>{t('welcome.message')}</div>; }
额外优化:缓存翻译数据
Next.js会自动缓存服务端组件的await调用(默认静态缓存),如果需要动态更新,可使用unstable_cache设置缓存策略:
import { unstable_cache } from 'next/cache'; const getCachedTranslations = unstable_cache( async () => { const brand = await getActiveBrand(); const translations = {} as { [key: string]: any }; brand.data.languages.forEach(lang => { translations[lang.language] = JSON.parse(JSON.parse(lang.languageData)); }); return translations; }, ['translations'], // 缓存标识键 { revalidate: 3600 } // 每小时重新验证一次缓存 ); // 在Layout中调用 const translations = await getCachedTranslations();
这种方案的优势:
- 翻译数据在服务端预获取,页面渲染时直接包含翻译内容,消除客户端加载延迟
- 复用Next.js服务端缓存机制,减少重复API请求
- 客户端组件无需额外请求,直接通过Context使用翻译数据
内容的提问来源于stack exchange,提问作者k_krrs
相关产品推荐
相关产品推荐

