You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:55:39