Next.js项目跳转页面编译超2000模块加载缓慢优化咨询
我有一个小型Next.js项目,首次启动后点击Link跳转至home/product/[slug]这类页面时,需等待约12秒,编译模块超2000个,其中product/[slug]页面编译模块达2900个。相关代码如下,请求提供加载提速方案:
Root Layout 代码(app/page.tsx)
'use client' import SideBar from "@/components/SideBar/SideBar"; import Header from "@/components/Header/Header"; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <div className="flex "> <div className="lg:block hidden"> <SideBar /> </div> <div className="lg:pl-64 pl-0 flex w-full h-full min-h-screen flex-col bg-slate-300"> <Header /> <div className="flex-1 text-black p-1 "> {children} </div> </div> </div> ); }
product/[slug] 页面代码
//app/product/[slug]/page.tsx "use client" import { productSchema } from '@/components/CRUDTAB/validatorComponent' import { AddProductField } from '@/data/ComponentData' import { apiAddProduct } from '@/data/apiUrl' import { columnProduct } from '@/data/listData'; import GetListComponent from '@/components/List/GetListComponent'; export default function Page({ params }: { params: { slug: string } }) { return ( <div> <GetListComponent slug={'product'} querySlug={'get'} columnData={columnProduct} AddDataField={AddProductField} dataSchema={productSchema} apiAddData={apiAddProduct} /> </div> ) }
GetListComponent 代码
// GetListComponent.tsx "use client" import { useSearchParams } from 'next/navigation' import React, { useEffect, useState } from 'react' import LoadingScene from '@/components/LoadingScene' import DataTable from '@/components/DataTable/DataTable' import AddComponent from '@/components/CRUDTAB/AddComponent' import getData from './getData' type Props = { slug: string, querySlug:string, columnData:any, AddDataField:any, dataSchema:any, apiAddData:string } export default function GetListComponent(props: Props) { const searchParams = useSearchParams(); const size = searchParams.get('size'); const page = searchParams.get('page'); const [data, setData] = useState<object[]>([]) const [loading,setLoading] = useState(true); useEffect(() => { const fetchData = async () => { try { const result = await getData(props.slug,props.querySlug, size, page); console.log("result",result) setData(result); } catch (error) { setData([]); } }; fetchData(); setLoading(false); }, [props.querySlug, size, page]); const [openAdd, setOpenAdd] = useState(false); return ( <div className='w-full h-full flex flex-col'> {props.slug} <div className='m-5'> <button onClick={() => setOpenAdd(!openAdd)} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> Add Customer </button> </div> {loading ? <LoadingScene /> : <DataTable columns={props.columnData} rows={data} slug={props.slug} />} <div> {openAdd && <AddComponent componentData={props.AddDataField} validValueSchema={props.dataSchema} slug={'Customer'} setOpen={setOpenAdd} apiUrl={props.apiAddData} />} </div> </div> ) }
加载提速方案
1. 将 RootLayout 改为服务器组件(核心优化)
你的RootLayout标记了'use client',这会导致整个路由树默认变成客户端组件,所有子页面和组件都会被打包到客户端,直接导致编译模块数量暴增。除非SideBar或Header必须使用客户端特性(比如useState、useRouter),否则移除'use client':
// app/page.tsx 移除'use client' import SideBar from "@/components/SideBar/SideBar"; import Header from "@/components/Header/Header"; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <div className="flex "> <div className="lg:block hidden"> <SideBar /> </div> <div className="lg:pl-64 pl-0 flex w-full h-full min-h-screen flex-col bg-slate-300"> <Header /> <div className="flex-1 text-black p-1 "> {children} </div> </div> </div> ); }
如果SideBar/Header需要客户端特性,仅在子组件中标记'use client',而不是在RootLayout上。比如把SideBar里需要客户端逻辑的部分抽离成独立的客户端组件,保持SideBar本身为服务器组件。
2. 服务器端预取数据,减少客户端逻辑
GetListComponent现在在客户端用useEffect获取数据,这不仅延迟页面渲染,还增加客户端编译量。可以将数据获取移到服务器组件中:
修改 product/[slug]/page.tsx 为服务器组件,预取数据后传给客户端组件:
// app/product/[slug]/page.tsx 移除'use client' import { productSchema } from '@/components/CRUDTAB/validatorComponent' import { AddProductField } from '@/data/ComponentData' import { apiAddProduct } from '@/data/apiUrl' import { columnProduct } from '@/data/listData'; import GetListComponent from '@/components/List/GetListComponent'; import getData from '@/components/List/getData'; export default async function Page({ params, searchParams }: { params: { slug: string }, searchParams: { size?: string; page?: string } }) { // 服务器端预取数据 const data = await getData('product', 'get', searchParams.size, searchParams.page); return ( <div> <GetListComponent slug={'product'} querySlug={'get'} columnData={columnProduct} AddDataField={AddProductField} dataSchema={productSchema} apiAddData={apiAddProduct} initialData={data} // 把预取的数据传给客户端组件 /> </div> ) }
修改 GetListComponent,直接使用初始数据:
// GetListComponent.tsx "use client" import { useSearchParams } from 'next/navigation' import React, { useState } from 'react' import LoadingScene from '@/components/LoadingScene' import DataTable from '@/components/DataTable/DataTable' import AddComponent from '@/components/CRUDTAB/AddComponent' import getData from './getData' type Props = { slug: string, querySlug:string, columnData:any, AddDataField:any, dataSchema:any, apiAddData:string, initialData: object[] // 添加初始数据参数 } export default function GetListComponent(props: Props) { const searchParams = useSearchParams(); const size = searchParams.get('size'); const page = searchParams.get('page'); const [data, setData] = useState<object[]>(props.initialData) const [loading,setLoading] = useState(false); // 初始不需要loading,因为已有数据 // 仅在searchParams变化时重新获取数据 const fetchData = async () => { setLoading(true); try { const result = await getData(props.slug,props.querySlug, size, page); setData(result); } catch (error) { setData([]); } finally { setLoading(false); } }; // 监听searchParams变化 React.useEffect(() => { fetchData(); }, [props.querySlug, size, page]); const [openAdd, setOpenAdd] = useState(false); return ( <div className='w-full h-full flex flex-col'> {props.slug} <div className='m-5'> <button onClick={() => setOpenAdd(!openAdd)} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> Add Customer </button> </div> {loading ? <LoadingScene /> : <DataTable columns={props.columnData} rows={data} slug={props.slug} />} <div> {openAdd && <AddComponent componentData={props.AddDataField} validValueSchema={props.dataSchema} slug={'Customer'} setOpen={setOpenAdd} apiUrl={props.apiAddData} />} </div> </div> ) }
3. 懒加载非必要组件
AddComponent只有在用户点击"Add Customer"后才会渲染,可以用React的lazy和Suspense懒加载它,减少初始编译的模块数量:
// GetListComponent.tsx 修改AddComponent的导入 import React, { useState, lazy, Suspense } from 'react' const AddComponent = lazy(() => import('@/components/CRUDTAB/AddComponent')) // 渲染部分替换为: <Suspense fallback={<div>Loading...</div>}> {openAdd && <AddComponent componentData={props.AddDataField} validValueSchema={props.dataSchema} slug={'Customer'} setOpen={setOpenAdd} apiUrl={props.apiAddData} />} </Suspense>
4. 开启TurboPack加速开发编译
Next.js 13+支持TurboPack,开发环境下编译速度比Webpack快很多。在next.config.js中开启:
/** @type {import('next').NextConfig} */ const nextConfig = { experimental: { turbo: true, }, } module.exports = nextConfig
5. 检查并优化第三方依赖
- 如果
DataTable使用了重型UI库(如MUI DataGrid、AntD Table),考虑替换为轻量替代方案(如react-table),或者按需导入库的部分模块,避免打包整个库。 - 检查
productSchema使用的Zod等验证库,确保是按需导入,而非导入整个库。
6. 启用Next.js的代码分割优化
Next.js默认会做代码分割,但可以确保你的组件结构合理,每个组件只导入必要的依赖,避免不必要的模块被打包。比如把通用的工具函数抽离到单独文件,避免重复导入。
内容的提问来源于stack exchange,提问作者CAMAP

