刚开始我也以为Next.js项目搭建就是npx create-next-app一把梭,结果真干起来才发现,从配置到部署每个环节都能踩出坑来。今天就把我最近从零搭建一个中型电商项目的过程掰开了说,包括那些让我半夜骂娘的bug和最终怎么解决的。
先说说环境:Node 18+,包管理器我用pnpm(npm慢到怀疑人生)。你要用yarn也行,但别用npm,真的。
开篇图:一张Next.js项目目录结构截图,标注了关键文件
第一步:别用默认模板,自己配
默认的create-next-app生成的代码太啰嗦了,一堆用不到的demo代码。我建议这样初始化:
“bash`
pnpm create next-app@latest my-app --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
为什么要这么写?--src-dir把代码放在src目录下,结构更清晰;--app用App Router(Pages Router已经过时了);--import-alias设置@别名,避免../../这种恶心路径。
装完依赖后第一件事:删掉app/page.tsx里默认的demo代码,只保留:
`tsx
export default function Home() {
return (
Hello World
)
}
/>
code>`
另一个坑:eslint配置。默认的eslint规则太松了,等于没配。我直接在.eslintrc.json加了这些:
`json`
{
"extends": ["next/core-web-vitals", "eslint:recommended"],
"rules": {
"no-unused-vars": "error",
"prefer-const": "error",
"no-console": "warn"
}
}
别问,问就是被未使用变量
坑过三次。
>第二步:环境变量配置,这个设计真的反人类
Next.js的环境变量机制是我见过最奇葩的设计之一。官方文档文档不够清晰,我踩了整整两天才搞明白。
关键规则就三条:
开头的变量会暴露给前端,其他的只在服务端可用 优先级最高,千万别提交到git这是我的配置模板:
`
.env.local(本地开发用,不提交)
DATABASE_URL="postgresql://localhost:5432/mydb"
NEXT_PUBLIC_API_URL="http://localhost:3000/api"
.env.production(生产环境)
NEXT_PUBLIC_API_URL="https://api.example.com"
`
还有个技巧:如果你想在build时读取环境变量,需要在next.config.js里用publicRuntimeConfig。但这玩意儿坑太多,我建议直接用process.env.NEXT_PUBLIC_*解决所有前端需求。
核心图:一张环境变量加载优先级流程图,展示了.env.local > .env.production > .env的覆盖关系
第三步:路由设计,App Router的坑比想象的多
App Router听起来很美,但实际用起来有不少反直觉的地方。先说layout的嵌套问题:
`tsx
// app/layout.tsx - 根布局
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
// app/dashboard/layout.tsx - 仪表盘布局
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
)
}
`
刚开始我以为layout会自动合并,结果发现子layout会完全覆盖父layout的部分内容。正确的理解是:layout是嵌套的,子layout只控制自己的children区域。
另一个让我崩溃的点:动态路由的params获取。在Pages Router里可以直接从props拿,App Router里必须这样:
`tsx
// app/posts/[id]/page.tsx
export default async function PostPage({ params }: { params: { id: string } }) {
// params是Promise,需要await
const { id } = await params
const post = await getPost(id)
return
}
`
等等,params是Promise?是的,Next.js 15开始,params改成了异步的。这设计真让人无语,但你必须适应。
第四步:数据获取,从3.2秒到0.8秒的优化
数据获取是Next.js的亮点,但用不好就是灾难。我一开始用fetch直接请求,结果每个页面都慢得像蜗牛。
先看错误做法:
`tsx
// ❌ 这样写会导致每个请求都重新执行
export default async function ProductsPage() {
const res = await fetch('https://api.example.com/products', {
cache: 'no-store' // 每次都重新请求
})
const products = await res.json()
return
}
`
正确做法是使用缓存策略:
`tsx
// ✅ 生产级写法
export default async function ProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }, // 每小时重新验证一次
}).then(res => {
if (!res.ok) throw new Error('获取商品列表失败')
return res.json()
})
return
}
// 或者用React Cache(需要自己实现)
import { cache } from 'react'
export const getProducts = cache(async () => {
const res = await fetch('https://api.example.com/products')
return res.json()
})
`
用了缓存后,页面加载时间从3.2秒降到了0.8秒。这提升太明显了,所以记住:能缓存就缓存,别让用户等。
还有个技巧:静态生成(SSG)和服务器端渲染(SSR)的选择。我的原则是:
- 内容很少变动的页面(比如关于我们、帮助中心)→ 用SSG
- 需要实时数据的页面(比如订单状态、用户信息)→ 用SSR或ISR
- 完全动态的内容(比如搜索功能)→ 用客户端组件 + API
第五步:部署,从Vercel到自托管
部署这事儿,Vercel确实方便,但如果你要省钱或者有合规要求,还是得自托管。我踩的坑主要是静态资源问题。
先说Vercel部署(最简单):
`bash
在项目根目录创建 vercel.json
{
"framework": "nextjs",
"buildCommand": "next build",
"outputDirectory": ".next"
}
`
然后vercel –prod就完事了。但注意:免费版的冷启动时间能到10秒以上,生产环境还是建议付费。
自托管的话,我的方案是Docker + Nginx:
`dockerfile
Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN pnpm install
COPY . .
RUN pnpm build
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
EXPOSE 3000
CMD ["pnpm", "start"]
`
这里有个坑:.next目录不能直接复制,必须用COPY –from=builder`完整路径。我之前踩过这个雷,结果build出来的东西跑不起来。
总结图:一张Next.js项目从开发到部署的完整流程图,标注了每个环节的关键点
总结一下,你可以立刻用的三个点
最后说一句:Next.js虽然坑多,但熟练掌握后绝对是生产级项目的利器。有问题直接看官方文档的示例代码,别看社区博客(包括我这篇,哈哈)。