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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 01:25:08