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

Next.js 14 App Router中SSR与数据获取的正确实现及疑问

Next.js 14 App Router SSR数据获取与状态管理方案

一、通用GraphQL数据获取助手函数改造

针对App Router的特性,直接在助手函数中配置fetch的缓存策略,同时保留复用性,改造后支持动态控制缓存模式,兼容SSR/ISR等场景:

// app/lib/storefront.js
export async function storefront(query, variables = {}, options = {}) {
  const defaultOptions = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Another-Property": "Something here",
    },
    body: JSON.stringify({
      query,
      variables,
    }),
    // 默认启用动态SSR数据获取(对应原getServerSideProps行为)
    ...{ cache: "no-store" },
    ...options,
  };

  const response = await fetch(process.env.NEXT_PUBLIC_API_URL, defaultOptions);

  if (!response.ok) {
    throw new Error(`GraphQL请求失败: ${response.statusText}`);
  }

  return response.json();
}
  • 新增query和variables参数,贴合GraphQL标准调用格式
  • 默认配置cache: "no-store"实现动态SSR,也可通过options覆盖(如cache: "force-cache"实现ISR)
  • 增加错误处理,避免静默失败

二、关于"use client"与SSR特性的说明

当你先在服务端组件获取数据,再将数据作为props传给标记了"use client"的客户端组件时:

  • 初始页面HTML由SSR生成,包含服务端获取的数据,完全保留SSR的SEO和首屏加载优势
  • 客户端组件中的hooks仅在浏览器接管后管理状态(如本地过滤、排序),不会影响初始SSR渲染结果
  • 核心逻辑:SSR负责"把数据写入HTML",客户端hooks负责"在浏览器中操作数据",两者互不冲突

三、完整实现方案:SSR获取数据 + 客户端hooks管理状态

1. 服务端页面/组件获取数据

App Router默认页面组件为服务端组件,直接调用通用助手函数获取数据:

// app/products/page.js
import { storefront } from "@/lib/storefront";
import ProductList from "./ProductList";

// 服务端组件:自动执行SSR逻辑
export default async function ProductsPage() {
  // 调用GraphQL查询商品列表
  const { data } = await storefront(`
    query GetProducts {
      products {
        id
        title
        price
      }
    }
  `);

  // 将服务端获取的数据传给客户端组件
  return <ProductList initialProducts={data.products} />;
}

2. 客户端组件用hooks管理状态

标记"use client",接收服务端传来的初始数据,用useState等hooks管理状态:

// app/products/ProductList.js
"use client";

import { useState } from "react";

export default function ProductList({ initialProducts }) {
  // 用服务端SSR的数据初始化状态
  const [products, setProducts] = useState(initialProducts);
  const [searchTerm, setSearchTerm] = useState("");

  // 客户端本地过滤逻辑
  const filteredProducts = products.filter(product =>
    product.title.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        placeholder="搜索商品"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <ul>
        {filteredProducts.map((product) => (
          <li key={product.id}>
            {product.title} - ${product.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

关键要点

  • 服务端组件负责数据获取,客户端组件负责交互逻辑,职责分离清晰
  • 初始渲染时,ProductList的内容已通过SSR嵌入HTML,搜索引擎可抓取完整商品列表
  • 客户端的搜索、状态更新均在浏览器端完成,不会触发服务端重新渲染

内容的提问来源于stack exchange,提问作者GM's

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 15:57:43