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
相关产品推荐
相关产品推荐

