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

