Next.js 中的数据获取模式演进:从 getServerSideProps 到 Server Components 的迁移路径

📅 2026/7/19 17:29:06
Next.js 中的数据获取模式演进:从 getServerSideProps 到 Server Components 的迁移路径
Next.js 中的数据获取模式演进从 getServerSideProps 到 Server Components 的迁移路径一、Pages Router 数据获取模式的局限与迁移需求治愈系应用最初基于 Next.js Pages Router 开发使用getServerSideProps在服务端获取数据。每个页面请求都触发完整的服务端渲染数据获取、HTML 生成、资源传输、客户端 Hydration 全部串行执行。首屏加载 3.9 秒中getServerSideProps的数据获取占 1.4 秒。迁移到 App Router 的 Server Components 后数据获取可以直接在组件顶层用async/await完成配合 Suspense 流式渲染数据获取不再阻塞整个页面的 HTML 生成。通过实测发现迁移后首屏 TTI 从 4.2 秒降至 2.5 秒且代码结构更清晰——数据获取逻辑从页面入口函数移入各组件内部。二、迁移路径与两种模式的对比分析从 Pages Router 到 App Router 的迁移不是一步到位而是逐页面替换。两种模式的核心差异体现在数据获取粒度与渲染策略上Pages Router 模式遵循页面级数据获取流程。首先通过getServerSideProps在页面入口获取数据随后执行完整的服务端渲染SSR等待全部数据就绪后生成 HTML。接着进行客户端 Hydration重建所有组件最终达到交互就绪状态此时 TTI 约为 4.2 秒。App Router 模式则采用组件级数据获取。利用 Server Components 在组件顶层通过async/await获取数据配合 Suspense 实现流式渲染分段输出内容。客户端仅对交互组件进行选择性 Hydration从而将 TTI 优化至 2.5 秒。具体的迁移步骤如下创建app/目录以启用 App Router。逐页面进行迁移确保业务连续性。将原有的getServerSideProps逻辑替换为组件内部的async函数。三、逐页面迁移的代码实践// Pages Router 版本getServerSideProps 模式 // 文件: pages/emotion-dashboard.tsx (旧版本) // 旧版所有数据在页面入口函数获取 interface EmotionDashboardProps { emotionData: EmotionData; userConfig: UserConfig; --- weatherInfo: WeatherInfo; } // 旧版数据获取串行 waterfall export async function getServerSideProps(context: GetServerSidePropsContext) { const userId context.query.userId as string; // 串行获取先配置 → 再情绪 → 最后天气 const userConfig await fetchUserConfig(userId); // 1.2s const emotionData await fetchEmotionData(userConfig); // 1.8s const weatherInfo await fetchWeatherInfo(userConfig.city); // 0.9s return { props: { emotionData, userConfig, weatherInfo }, }; } function EmotionDashboard({ emotionData, userConfig, weatherInfo }: EmotionDashboardProps) { return ( div EmotionChart data{emotionData} / WeatherBackground info{weatherInfo} / UserGreeting config{userConfig} / /div ); } // App Router 版本Server Components Suspense // 文件: app/emotion-dashboard/page.tsx (新版本) // 新版各组件独立获取数据Suspense 流式渲染 function EmotionDashboardPage({ params }: { params: { userId: string } }) { return ( div classNameemotion-dashboard {/* 无需 Suspense纯服务端组件直接渲染 */} UserGreeting userId{params.userId} / {/* Suspense独立数据获取不阻塞整体 */} Suspense fallback{EmotionChartFallback /} EmotionChart userId{params.userId} / /Suspense Suspense fallback{WeatherFallback /} WeatherBackground userId{params.userId} / /Suspense /div ); } // 各组件独立获取数据并行而非串行 async function UserGreeting({ userId }: { userId: string }) { const config await fetchUserConfig(userId); return h1{config.displayName} 的情绪趋势/h1; } async function EmotionChart({ userId }: { userId: string }) { // 组件内直接获取无需 getServerSideProps const config await fetchUserConfig(userId); const data await fetchEmotionData(config); return MoodTrendVisualization data{data} /; } async function WeatherBackground({ userId }: { userId: string }) { const config await fetchUserConfig(userId); const info await fetchWeatherInfo(config.city); return WeatherLayer info{info} /; } // 数据获取函数与旧版相同无需修改 async function fetchUserConfig(userId: string): PromiseUserConfig { const res await fetch(/api/user/config?userId${userId}, { next: { revalidate: 3600 }, }); if (!res.ok) throw new Error(配置获取失败: ${res.status}); return res.json(); } // 迁移检查清单Pages → App Router 的关键变更 // 设计意图逐页面迁移需要检查以下项目 # 避免遗漏导致运行时错误 const migrationChecklist [ 1. 创建 app/ 目录结构, 2. 页面文件从 .tsx 移入 app/[route]/page.tsx, 3. getServerSideProps → 组件内 async/await, 4. getStaticProps → generateStaticParams revalidate, 5. getStaticPaths → generateStaticParams, 6. useRouter → useParams / useSearchParams, 7. _app.tsx layout → app/layout.tsx, 8. API Routes → app/api/[route]/route.ts, 9. useState/useEffect 组件标记 use client, 10. 页面级状态 → React Context (客户端层), ]; function validateMigration(oldPagePath: string, newPagePath: string): string[] { const warnings: string[] []; // 检查旧文件是否仍包含 getServerSideProps // 检查新文件是否遗漏 use client 标记 // 实际实现通过 AST 解析完成 return warnings; }四、迁移的风险控制与 Pages/App 双路由共存边界逐页面迁移的核心风险是Pages Router 和 App Router 共存期间两个路由系统同时运行。Next.js 支持两种路由共存pages/ 和 app/ 目录同时存在但同一路径只能在两个系统中选一个——pages/emotion-dashboard.tsx和app/emotion-dashboard/page.tsx不能同时存在。迁移策略是先创建app/目录逐步将页面从pages/移入app/每移入一个页面就删除pages/中对应的文件。迁移顺序按风险从低到高先迁移纯展示页面无交互再迁移带交互的页面需标记use client最后迁移 API 路由。共存期间的 API 路由不变pages/api/ 和 app/api/ 可以共存但路径不同避免 API 端点变更影响前端调用。五、总结Pages Router 到 App Router 迁移的关键要点逐页面迁移按风险从低到高排序先展示页面、再交互页面、最后 API 路由数据获取重构getServerSideProps→ 组件内async/await从页面级串行改为组件级并行Suspense 流式渲染各组件独立数据获取Suspense 边界隔离慢数据TTI 从 4.2s 降至 2.5s双路由共存Pages 和 App 路由同时运行同路径不共存逐页面移入后删除旧文件客户端标记使用 useState/useEffect 的组件必须标记use client否则服务端报错生产落地步骤创建 app/ 目录 → 制定迁移顺序 → 迁移纯展示页面 → 迁移交互页面标记use client→ 配置 Suspense 边界 → 迁移 API 路由 → 删除 pages/ 旧文件 → 全量测试。