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

Next.js项目跳转页面编译超2000模块加载缓慢优化咨询

Next.js 页面首次加载缓慢问题:编译模块过多导致12秒等待

我有一个小型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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:34:57