本文是基于 nextjs v14 版本进行展示。
最快捷的创建 Next.js 项目的方式是使用 create-next-app脚手架,你只需要运行:
js
npx create-next-app@14
推荐使用 tailwindcss ,这也是 Next.js 推荐的 CSS 方案,很多 example 都会用它。
运行项目
查看项目根目录 package.json 文件的代码:
js
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
开发的时候使用 npm run dev。部署的时候先使用 npm run build 构建生产代码,再执行 npm run start 运行生产项目。运行 npm run lint 则会执行 ESLint 语法检查。
next build
执行 next build 将会创建项目的生产优化版本:

从上图可以看出,构建时会输出每条路由的信息,比如 Size 和 First Load JS。注意这些值指的都是 gzip 压缩后的大小。其中 First Load JS 会用绿色、黄色、红色表示,绿色表示高性能,黄色或红色表示需要优化。
这里要解释一下 Size 和 First Load JS 的含义。
正常我们开发的 Next.js 项目,其页面表现类似于单页应用,即路由跳转(我们称之为"导航")的时候,页面不会刷新,而会加载目标路由所需的资源然后展示,所以:
js
加载目标路由一共所需的 JS 大小 = 每个路由都需要依赖的 JS 大小(shared by all) + 目标路由单独依赖的 JS 大小
其中:
- 加载目标路由一共所需的 JS 大小就是
First Load JS - 目标路由单独依赖的 JS 大小就是
Size - 每个路由都需要依赖的 JS 大小就是图中单独列出来的
First load JS shared by all
以上图中的 / 路由地址为例,87.6 kB(First Load JS)= 533 B(Size) + 87.1 kB(First load JS shared by all)
next start
生产模式下,使用 next start运行程序。不过要先执行 next build构建出生产代码。运行的时候,跟开发模式相同,程序默认开启在 http://localhost:3000。如果你想更改端口号:
js
npx next start -p 4000
app Router
app router 的目录结构类似于:
js
src/
└── app
├── page.js
├── layout.js
├── template.js
├── loading.js
├── error.js
└── not-found.js
├── about
│ └── page.js
└── more
└── page.js
定义路由(Routes)
首先是定义路由,文件夹被用来定义路由。
每个文件夹都代表一个对应到 URL 片段的路由片段。创建嵌套的路由,只需要创建嵌套的文件夹。举个例子,下图的 app/dashboard/settings目录对应的路由地址就是 /dashboard/settings:

定义页面(Pages)
那如何保证这个路由可以被访问呢?你需要创建一个特殊的名为 page.js 的文件。至于为什么叫 page.js呢?除了 page 有"页面"这个含义之外,你可以理解为这是一种约定或者规范。

app/page.js对应路由/app/dashboard/page.js对应路由/dashboardapp/dashboard/settings/page.js对应路由/dashboard/settingsanalytics目录下因为没有page.js文件,所以没有对应的路由。这个文件可以被用于存放组件、样式表、图片或者其他文件。
那 page.js 的代码该如何写呢?最常见的是展示 UI,比如:
js
// app/page.js
export default function Page() {
return <h1>Hello, Next.js!</h1>
}
定义布局(Layouts)
布局是指多个页面共享的 UI。
在导航的时候,布局会保留状态、保持可交互性并且不会重新渲染,比如用来实现后台管理系统的侧边导航栏。
定义一个布局,你需要新建一个名为 layout.js的文件,该文件默认导出一个 React 组件,该组件应接收一个 children prop,chidren 表示子布局(如果有的话)或者子页面。
举个例子,我们新建目录和文件如下图所示:

相关代码如下:
js
// app/dashboard/layout.js
export default function DashboardLayout({
children,
}) {
return (
<section>
<nav>nav</nav>
{children}
</section>
)
}
// app/dashboard/page.js
export default function Page() {
return <h1>Hello, Dashboard!</h1>
}
同一文件夹下如果有 layout.js 和 page.js,page 会作为 children 参数传入 layout。换句话说,layout 会包裹同层级的 page。
定义加载界面(Loading UI)
在dashboard 目录下我们新建一个 loading.js。

loading.js的代码如下:
js
// app/dashboard/loading.js
export default function DashboardLoading() {
return <>Loading dashboard...</>
}
同级的 page.js 代码如下:
js
// app/dashboard/page.js
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 3000))
return {
message: 'Hello, Dashboard!',
}
}
# 一定要是async定义的组件,不然loading.js不会生效
export default async function DashboardPage(props) {
const { message } = await getData()
return <h1>{message}</h1>
}
不再需要其他的代码,loading 的效果就实现了。
就是这么简单。其关键在于 page.js导出了一个 async 函数。
loading.js 的实现原理是将 page.js和下面的 children 用 <Suspense> 包裹。因为page.js导出一个 async 函数,Suspense 得以捕获数据加载的 promise,借此实现了 loading 组件的关闭。

定义 404 页面
顾名思义,当该路由不存在的时候展示的内容。
Next.js 项目默认的 not-found 效果如下:

如果你要替换这个效果,只需要在 app 目录下新建一个 not-found.js,代码示例如下: