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

Next.js 14:如何将服务端配置数据存入Signals供客户端组件使用?

Next.js 14服务端配置存入Signals后客户端无法读取的解决方法

问题背景

在Next.js 14的app目录layout.tsx中,从服务端获取应用配置数据并存入Preact Signals,服务端确认数据已成功设置,但客户端组件中settings.value始终为空。因配置对象结构复杂,不想使用useContext,寻求解决方案,同时询问是否直接从服务端数据库获取数据更优。

示例代码

layout.tsx

import { Inter } from "next/font/google";
import { fetchRestaurantSettings } from '@/services/fetchRestaurantSettings';
import { setSettings } from '@/signals/SettingsSignals';

const inter = Inter({ subsets: ["latin"] });

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const settings = await fetchRestaurantSettings();

  setSettings(settings);

  return (
    <html lang='en'>
      <body className={inter.className}>
        {children}
      </body>
    </html>
  );
}

Signals代码(SettingsSignals.ts)

import { signal, computed } from '@preact/signals-react';
import { RestaurantSettings } from '@/types/RestaurantSettings';

export const settingsData = signal<RestaurantSettings | null>(null);

export function setSettings(data: RestaurantSettings) {
  console.log(data); // 服务端能正常输出数据
  return settingsData.value = data;
}

export const settings = computed(() => settingsData.value);

客户端测试组件(Test.tsx)

'use client';

import React from 'react';
import { settings, settingsData } from '@/signals/SettingsSignals';

const Test: React.FC = () => {
  console.log(settingsData.value); // 输出null
  console.log(settings); // 输出空Proxy
  
  return <>test</>;
};

export default Test;

问题原因

Next.js的服务端组件和客户端组件运行在完全隔离的环境中:服务端组件中的Signals实例仅存在于服务端内存,客户端渲染时会创建全新的Signals实例,所以客户端无法读取服务端设置的值。

解决方案

方法1:通过全局变量注入初始化值(推荐)

将服务端获取的配置序列化后注入客户端全局变量,在客户端初始化Signals时读取该变量,确保客户端Signals拥有初始值。

修改layout.tsx

import { Inter } from "next/font/google";
import { fetchRestaurantSettings } from '@/services/fetchRestaurantSettings';

const inter = Inter({ subsets: ["latin"] });

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const settings = await fetchRestaurantSettings();

  return (
    <html lang='en'>
      <body className={inter.className}>
        {/* 将配置注入客户端全局变量 */}
        <script
          dangerouslySetInnerHTML={{
            __html: `window.__INITIAL_SETTINGS__ = ${JSON.stringify(settings)};`,
          }}
        />
        {children}
      </body>
    </html>
  );
}

修改Signals代码

import { signal, computed } from '@preact/signals-react';
import { RestaurantSettings } from '@/types/RestaurantSettings';

// 客户端初始化时读取全局变量,服务端环境下为null
const initialValue = typeof window !== 'undefined' 
  ? (window as Window & { __INITIAL_SETTINGS__: RestaurantSettings }).__INITIAL_SETTINGS__ 
  : null;

export const settingsData = signal<RestaurantSettings | null>(initialValue);

export function setSettings(data: RestaurantSettings) {
  console.log(data);
  return settingsData.value = data;
}

export const settings = computed(() => settingsData.value);

方法2:通过props逐层传递(适合组件层级较浅的场景)

如果客户端组件层级不深,可以直接在layout中通过props将settings传递给子组件,再逐层传递到目标客户端组件。但该方法不适合复杂组件树。

关于是否直接从服务端数据库获取数据

直接让客户端连接数据库绝对不可行,会导致数据库凭证泄露,存在严重安全风险。

合理的做法:

  • 若配置数据更新频率低:优先使用服务端组件获取后注入客户端的方式,减少客户端请求,提升性能。
  • 若配置数据需要实时更新:在客户端组件中通过API接口请求服务端获取最新配置,同时做好缓存策略(如revalidate或客户端缓存)。

内容的提问来源于stack exchange,提问作者axlpl

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:37:10