NextJS13 App目录:服务端传数据到客户端及客户端URL获取方案
解决方案
一、客户端安全获取URL
你的window is not defined错误源于在服务端渲染阶段尝试访问window,以及错误地将window放入useEffect依赖数组。以下是修正方案:
正确的自定义Hook实现
"use client"; import { useEffect, useState } from 'react'; import useSWR from "swr"; import apiEndpoints from "./endpoints"; import getResponse from "./getResponse"; export const useHotelAuth = () => { const { auth } = apiEndpoints.GET; const [domain, setDomain] = useState<string | null>(null); useEffect(() => { // useEffect仅在客户端执行,可安全访问window const currentDomain = window.location.host || "localhost:3000"; setDomain(currentDomain); }, []); // 空依赖数组,仅执行一次 // 仅当domain存在时发起SWR请求,避免服务端报错 const { data, isLoading, error } = useSWR( domain ? `${auth}/${domain}` : null, getResponse, { revalidate: 3600 } // 和服务端缓存策略保持一致 ); return { hotelAuthData: data, hotelAuthLoading: isLoading, error }; };
核心要点:
- 用
useState存储域名,在useEffect中获取window.location.host(仅客户端执行) - SWR请求的key仅在域名存在时生成,避免服务端渲染阶段触发无效请求
- 不要将
window放入useEffect依赖数组
二、将服务端数据传递到客户端并存入Zustand
避免重复调用API,直接复用generateMetadata中已获取的hotelAuth数据,通过服务端组件传props到客户端组件,再存入Zustand。
步骤1:抽离服务端数据获取逻辑
将generateMetadata中的数据请求逻辑抽成独立函数,方便复用:
// app/lib/hotelAuth.ts import { headers } from 'next/headers'; // 定义类型(可选,推荐) type HotelAuthData = { data: { hotel_name: string; hotel_description: string; // 其他字段... }; }; export async function getHotelAuth(): Promise<HotelAuthData | null> { const headersList = headers(); const domain = headersList.get("x-forwarded-host") || "localhost:3000"; try { const res = await fetch( `https://apidomain.com/parameter/${domain}`, { next: { revalidate: 3600 } } // 缓存1小时 ); if (!res.ok) throw new Error('Failed to fetch hotel auth data'); return res.json(); } catch (err) { console.error('Error fetching hotel auth:', err); return null; } }
步骤2:在页面组件和generateMetadata中复用函数
// app/[id]/page.tsx import { Metadata, ResolvingMetadata } from 'next'; import { getHotelAuth } from '../lib/hotelAuth'; import ClientComponent from './ClientComponent'; type MetadataProps = { params: { id: string }; searchParams: Record<string, string | string[]>; }; // 生成页面元数据 export async function generateMetadata( { params, searchParams }: MetadataProps, parent: ResolvingMetadata ): Promise<Metadata> { const hotelAuth = await getHotelAuth(); return { title: hotelAuth?.data?.hotel_name || 'Default Title', description: hotelAuth?.data?.hotel_description || 'Default Description', openGraph: { title: hotelAuth?.data?.hotel_name || 'Default OG Title', description: hotelAuth?.data?.hotel_description || 'Default OG Description', }, }; } // 服务端页面组件 export default async function Page({ params }: { params: { id: string } }) { const hotelAuth = await getHotelAuth(); // Next.js会自动缓存fetch请求,不会重复调用API return ( <div> {/* 将服务端数据传递给客户端组件 */} <ClientComponent initialHotelAuth={hotelAuth} /> </div> ); }
步骤3:客户端组件存入Zustand
首先定义Zustand状态仓库:
// app/store/hotelStore.ts import { create } from 'zustand'; import type { HotelAuthData } from '../lib/hotelAuth'; type HotelStoreState = { hotelAuthData: HotelAuthData | null; setHotelAuthData: (data: HotelAuthData | null) => void; }; export const useHotelStore = create<HotelStoreState>((set) => ({ hotelAuthData: null, setHotelAuthData: (data) => set({ hotelAuthData: data }), }));
然后客户端组件接收数据并存入状态:
// app/[id]/ClientComponent.tsx "use client"; import { useEffect } from 'react'; import { useHotelStore } from '../store/hotelStore'; import type { HotelAuthData } from '../lib/hotelAuth'; type ClientComponentProps = { initialHotelAuth: HotelAuthData | null; }; export default function ClientComponent({ initialHotelAuth }: ClientComponentProps) { const setHotelAuthData = useHotelStore((state) => state.setHotelAuthData); useEffect(() => { // 将服务端传递的数据存入Zustand if (initialHotelAuth) { setHotelAuthData(initialHotelAuth); } }, [initialHotelAuth, setHotelAuthData]); // 示例:从Zustand读取数据 const hotelAuthData = useHotelStore((state) => state.hotelAuthData); return ( <div> {hotelAuthData ? ( <div> <h1>{hotelAuthData.data.hotel_name}</h1> <p>{hotelAuthData.data.hotel_description}</p> </div> ) : ( <p>Loading...</p> )} </div> ); }
其他客户端组件只需通过useHotelStore即可获取服务端预取的hotelAuth数据,无需重复请求API。
内容的提问来源于stack exchange,提问作者Ashutosh Dash
相关产品推荐
相关产品推荐

