NodeJS系列(8)- Next.js 框架 (一) | 安装配置、路由(Routing)、页面布局(Layout)
Next.js 是一个用于构建 Web 应用程序的框架。Next.js 是一个用于生产环境的 React 框架,是一个 React 服务端渲染应用框架。
Next.js 具有同类框架中最佳的 “开发人员体验” 和许多内置功能,它的特点如下:
(1) 直观的、 基于页面 的路由系统(并支持 动态路由);
(2) 预渲染,支持在页面级的 静态生成 (SSG) 和 服务器端渲染 (SSR)
(3) 自动代码拆分,提升页面加载速度;
(4) 具有经过优化的预取功能的 客户端路由;
(5) 内置 CSS 和 Sass 的支持,并支持多数 CSS-in-JS 库;
(6) 开发环境支持快速刷新;
(7) 利用 Serverless Functions 及 API 路由 构建 API 功能;
(8) 完全可扩展;
Next.js 被用于数以万计的的网站和 Web 应用程序,包括许多世界上许多最大的品牌都在使用 Next.js。
NextJS: https://nextjs.org/
NextJS GitHub: https://github.com/vercel/next.js
React: https://www.reactjs.org/ (https://react.dev)
React GitHub: https://github.com/facebook/react
1. 系统环境
操作系统:CentOS 7.9 (x64)
NodeJS: 16.20.0
NPM: 8.19.4
NVM: 0.39.2
NextJS: 13.4.12
2. 创建 Next.js 项目
安装 create-next-app 脚手架,命令如下:
# 使用 -g 参数,表示该命令只需在本机上运行一次
$ npm install -g create-next-app
...
使用 create-next-app 命令创建 NextJS 项目,命令如下:
$ create-next-app nextjs-demo
√ Would you like to use TypeScript? ... No √ Would you like to use ESLint? ... No √ Would you like to use Tailwind CSS? ... No √ Would you like to use `src/` directory? ... Yes √ Would you like to use App Router? (recommended) ... No √ Would you like to customize the default import alias? ... Yes Creating a new Next.js app in /home/develop/nodejs/nextjs-demo. Using npm. Initializing project with template: default Installing dependencies: - react - react-dom - next added 23 packages in 5s Initialized a git repository. Success! Created nextdemo at /home/develop/nodejs/nextjs-demo
注:在 Next.js 13 之前,Pages Router 是在 Next.jsp 中创建路由的主要方式。它使用直观的文件系统路由器将每个文件映射到一个路由。新版本的 Next.js 仍然支持 Pages Router,Next.js 官方建议迁移到新的 App Router,以利用 React 的最新功能。本文主要介绍基于 Pages Router 的 Web 应用,所以第 5 个选项选了 No,表示选择了 Pages Router。
也可以使用 npx 命令直接创建 NextJS 项目,命令格式如下:
$ npx create-next-app nextjs-demo
注:npx 是 npm 5.2.0 及更高版本中包含的一个命令行工具,用于执行本地安装的或在线安装的 Node.js 包中的命令。npm 主要用于管理和安装依赖包,而 npx 主要用于运行本地或在线安装的包中的命令。
3. 运行 Next.js 项目
1) 项目目录结构
整个项目的目录结构:
├── public │ ├── favicon.ico # Favicon │ ├── next.svg │ └── vercel.svg ├── src │ ├── pages # Page Router │ │ ├── api │ │ │ └── hello.js │ │ ├── _app.js │ │ ├── _document.js │ │ └── index.js # home Page │ │ │ └── styles │ ├── globals.css │ └── Home.module.css │ ├── jsconfig.json # JavaScript 配置文件 ├── next.config.js # Next.js 配置文件 ├── package-lock.json # 项目依赖和脚本(锁定),运行 npm install 后自动生成 ├── package.json # 项目依赖和脚本 └── README.md
2) 启动项目
进入 nextjs-demo 目录安装依赖,命令如下:
$ npm install
...
开发模式运行,命令如下:
$ npm run dev
> nextjs-demo@0.1.0 dev > next dev - ready started server on 0.0.0.0:3000, url: http://localhost:3000
...
生产模式运行,命令如下:
$ npm run build
> nextjs-demo@0.1.0 build > next build ...
$ npm run start
> nextjs-demo@0.1.0 start > next start - ready started server on 0.0.0.0:3000, url: http://localhost:3000 ...
使用浏览器访问 http://ip:3000,本机上可以访问 http://localhost:3000。
4. 路由 (Routing)
页面路由器 (Page Router) 有一个基于页面概念的文件系统路由器。当一个文件被添加到 src/pages (或 pages) 目录时,它会自动作为路由使用。
在 Next.js 中,页面是从 src/pages (或 pages) 目录中的 .js、.jsx、.ts 或 .tsx 文件导出的 React 组件,并且每个页面都根据其文件名与一个路由相关联。
1) 文件名路由
在 src/pages 目录下创建 test.js 文件,内容如下:
export default (props) => { return ( <div>Test Page</div> ) }
使用浏览器访问 http://localhost:3000/test,显示内容如下:
Test Page
显然 /test 被路由到 src/pages/test.js 文件,这里的路径是大小写敏感的,即 /TEST 和 /test 不是同一路径。
2) 索引路由
路由将自动将名为 index 的文件映射到 index 所在目录,具体规则如下:
src/pages/index.js -> /
src/pages/hello/index.js /hello
在 src/pages 目录下创建 hello 子目录,并在 hello 目录下创建 index.js 文件,内容如下:
export default (props) => { return ( <div>Hello - Index Page</div> ) }
使用浏览器访问 http://localhost:3000/hello,显示内容如下:
Hello - Index Page
3) 嵌套路由 (多级目录+文件名)
路由支持嵌套文件,具体规则如下:
src/pages/hello/world.js -> /hello/world
src/pages/hello/nested/router.js -> /hello/nested/router
在 src/pages 目录下创建 hello/nested 两级子目录,并在 nested 目录下创建 router.js 文件,内容如下:
export default (props) => { return ( <div>Hello/nested - router Page</div> ) }
使用浏览器访问 http://localhost:3000/hello/nested/router,显示内容如下:
Hello/nested - router Page
4) 动态路由
Next.js 支持具有动态路由的页面,具体规则如下:
src/pages/hello/[id].js -> /hello/*
在 src/pages/hello 目录下创建 "[id].js" 文件,内容如下:
import { useRouter } from 'next/router' export default () => { const router = useRouter() return <p>Hello: {router.query.id}</p> }
使用浏览器访问 http://localhost:3000/hello/5,显示内容如下:
Hello: 5
5) API 路由
使用 App Router 的项目,可以直接使用服务器组件或路由处理程序,不需要使用 API 路由。这里的 API 路由,是指在使用了 Page Router 的项目里,在文件夹 src/pages/api 目录中的任何文件,都会被自动映射到 /api/*,并将被视为 API 端点而不是页面。
上文的 nextjs-demo 项目里,create-next-app 命令自动创建了文件 src/pages/api/hello.js,它就是一个 API 端点,内容如下:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction export default function handler(req, res) { res.status(200).json({ name: 'John Doe' }) }
使用浏览器访问 http://localhost:3000/api/hello,显示内容如下:
{"name":"John Doe"}
API 路由在 next.config.js 中的相关配置属性是 pageExtensions。
5. 页面布局 (Layout)
React 模型允许我们将页面解构为一系列组件,其中许多组件经常在页面之间重复使用,比如在每一页上都有相同的导航栏和页脚等。
1) App 组件
Next.js 使用 App 组件来初始化页面。App 组件除了控制页面初始化之外,还能完成如下功能:
(1) 在页面更改之间创建共享布局
(2) 将附加数据注入页面
(3) 添加全局 CSS
(1) 自定义 App 组件
可以在 src/pages 目录下添加 _app.js 文件,内容如下:
export default ({ Component, pageProps }) => { return <Component {...pageProps} /> }
其中 Component 是动态页面(或当前页面),导航切换页面时,Component 都会更改为导航后的新页面。pageProps 是一个带有初始属性的对象,这些属性是通过某种数据获取方法为页面预加载的,否则它就是一个空对象。
注:
(1) 如果 App 组件正在运行,并且添加了自定义 App 组件(即新增了 src/pages/_app.js 文件),则需要重新启动 NextJS 服务。
(2) App 组件 不支持的 Next.js 数据获取方法,如 getStaticProps 或 getServerSideProps。
(2) getInitialProps 属性
在 App 组件中可以使用 getInitialProps 将对没有 getStaticProps 的页面禁用自动静态优化。
import App from 'next/app' export default function MyApp({ Component, pageProps, example }) { return ( <> <p>Data: {example}</p> <Component {...pageProps} /> </> ) } MyApp.getInitialProps = async (context) => { const ctx = await App.getInitialProps(context) return { ...ctx, example: 'data' } }
不建议使用此模式。相反,可以考虑逐步采用 App 路由器,这样可以更容易地获取页面和布局的数据。
2) Document 组件
Document 组件可以更新用于渲染 (render) 页面的 <html> 和 <body> 标记。
(1) 自定义 Document 组件
可以在 src/pages 目录下添加 _document.js 文件,内容如下:
import { Html, Head, Main, NextScript } from 'next/document' export default function Document() { return ( <Html lang="en"> <Head /> <body> <Main /> <NextScript /> </body> </Html> ) }
注:
(1) Document 组件仅在服务器上渲染 (render),因此 onClick 这类的事件处理程序不能在此文件中使用。
(2) 要正确渲染页面,需要 <Html>、<Head/>、<Main/> 和 <NextScript/>。
(3) _document.js 中使用的 <Head/> 组件与 next/head 不同。此处使用的 <Head/> 组件应仅用于所有页面通用的任何 <Head> 代码。对于所有其他情况,如<title>标记,建议在页面或组件中使用 next/head 。
(4) <Main/> 之外的 React 组件不会被浏览器初始化。不要在此处添加应用程序逻辑或自定义 CSS(如样式 jsx)。
(5) Document 组件不支持的 Next.js 数据获取方法,如 getStaticProps 或 getServerSideProps。
(2) 自定义 renderPage
自定义 renderPage 是高级选项,只有在需要 CSS-in-JS 等库来支持服务器端渲染时才需要,对于内置样式的 jsx 支持来说是不需要的。
import Document, { Html, Head, Main, NextScript } from 'next/document' class MyDocument extends Document { static async getInitialProps(ctx) { const originalRenderPage = ctx.renderPage // Run the React rendering logic synchronously ctx.renderPage = () => originalRenderPage({ // Useful for wrapping the whole react tree enhanceApp: (App) => App, // Useful for wrapping in a per-page basis enhanceComponent: (Component) => Component, }) // Run the parent `getInitialProps`, it now includes the custom `renderPage` const initialProps = await Document.getInitialProps(ctx) return initialProps } render() { return ( <Html lang="en"> <Head /> <body> <Main /> <NextScript /> </body> </Html> ) } } export default MyDocument
不建议使用此模式。相反,可以考虑逐步采用 App Router,这样可以更容易地获取页面和布局的数据。
注:
(1) 在客户端转换期间,不会调用 _document 中的 getInitialProps。
(2) _document 的 ctx 对象等效于在 getInitialProps 中接收到的对象,添加了 renderPage。
3) 自定义页面布局
在上文 nextjs-demo 项目基础上,创建 src/components 目录,这里 components 和 pages 目录在同一级目录中。
创建 src/components/layout.js 文件,内容如下:
import Navbar from './navbar' import Footer from './footer' export default function Layout({ children }) { return ( <> <Navbar /> <main>{children}</main> <Footer /> </> ) }
创建 src/components/navbar.js 文件,内容如下:
export default () => { return ( <> <div>Navbar</div> </> ) }
创建 src/components/footer.js 文件,内容如下:
export default () => { return ( <> <div>Footer</div> </> ) }
修改 src/pages/_app.js 文件,内容如下:
import Layout from '../components/layout' export default ({ Component, pageProps }) => { return ( <Layout> <Component {...pageProps} /> </Layout> ) }
使用浏览器访问 http://localhost:3000/test,显示内容如下:
Navbar
Test Page
Footer
4) Errors 页面
(1) 404 页面
404 页面可能经常被访问。服务器为每次访问呈现一个错误页面会增加 Next.js 服务器的负载,这可能导致成本增加和体验缓慢。
为了避免上述陷阱,Next.js 默认情况下提供了一个静态 404 页面,而无需添加任何额外的文件。
自定义 404 页面,可以在 src/pages 添加 404.js 文件,内容如下:
export default function Custom404() {
return <h1>404 - Page Not Found</h1>
}
注:如果需要在构建时获取数据,可以在此页面中使用 getStaticProps。
(2) 500 页面
服务器为每次访问呈现错误页面会增加响应错误的复杂性。为了帮助用户尽快获得对错误的响应,Next.js 默认情况下提供了一个静态 500 页面,而无需添加任何其他文件。
自定义 500 页面,可以在 src/pages 目录下添加 500.js 文件,内容如下:
export default function Custom500() {
return <h1>500 - Server-side error occurred</h1>
}
注:如果需要在构建时获取数据,可以在此页面中使用 getStaticProps。
(3) Error 组件
Error 组件同时处理客户端和服务器端的 500 错误。
自定义 Error 组件,可以在 src/pages 目录下添加 _error.js 文件,内容如下:
function Error({ statusCode }) { return ( <p> {statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'} </p> ) } Error.getInitialProps = ({ res, err }) => { const statusCode = res ? res.statusCode : err ? err.statusCode : 404 return { statusCode } } export default Error
_error.js 仅在生产环境中使用。在开发过程中,一般调用堆栈中得到一个错误,以了解错误的来源。
(4) 重用内置错误页
要渲染内置错误页面,可以通过导入 Error 组件,格式如下:
import Error from 'next/error' export async function getServerSideProps() { const res = await fetch('https://api.github.com/repos/vercel/next.js') const errorCode = res.ok ? false : res.status const json = await res.json() return { props: { errorCode, stars: json.stargazers_count }, } } export default function Page({ errorCode, stars }) { if (errorCode) { return <Error statusCode={errorCode} /> } return <div>Next stars: {stars}</div> }
注:
Error 组件不支持 Next.js 数据获取方法,如 getStaticProps 或 getServerSideProps。
_error 和 _app 一样,是一个保留的路径名,_error 用于定义错误页面的自定义布局和行为。当访问路径是 /_error 时,会自动跳转到 404 页面。
6. 使用 Ant Design
Ant-Design 是采用 React 封装了一套 Ant Design 的组件库。Ant Design 的模块化设计方式,用户可以快速建立一个新的应用程序,它还提供丰富的 UI 组件和高可定制,用户可以完全按照自己的需求来进行个性化设置。
Ant Design 支持跨端统一设计,并可以满足开发者因技术栈而异的不同设计习惯,能够帮助他们实现跨端开发的融合,加快项目实施的进度。Ant Design 还提供了丰富的文档,从概念引导到实践指南,用户可以利用文档获取设计新灵感,同时也提升了自己对软件设计,交互和用户体验的认知水平。
Ant Desing: https://ant.design/index-cn
1) 安装 Ant Design
进入 NextJS 项目目录,运行如下命令:
$ npm install antd
命令运行成功后,查看 package.json 文件,在 "dependencies" 部分多了如下内容:
{ ... "dependencies": { "antd": "^4.5.2", ... } }
以上命令安装了 latest 版的 antd,可以指定安装版本,本文使用 npm install antd@4.5.2 命令安装 Ant Design 4.5.2 。
也可以先修改 package.json 文件,指定 antd 的版本,比如 "antd": "^4.5.2",再运行 npm install 命令。
2) 使用 antd 组件
在上文 nextjs-demo 项目基础上,修改 src/pages/_app.js 文件,内容如下:
import 'antd/dist/antd.min.css'; import Layout from '../components/layout' export default ({ Component, pageProps }) => { return ( <Layout> <Component {...pageProps} /> </Layout> ) }
注:添加了 antd/dist/antd.min.css
修改 src/components/navbar.js 文件,内容如下:
import React, { useState } from 'react'; import { HomeOutlined, LoginOutlined, SettingOutlined } from '@ant-design/icons'; import { Menu } from 'antd'; const items = [ { label: 'Home', key: 'home', icon: <HomeOutlined />, }, { label: 'Login', key: 'login', icon: <LoginOutlined />, }, ]; const Navbar = () => { const [current, setCurrent] = useState('home'); const onClick = (e) => { //console.log('click ', e); setCurrent(e.key); }; return <Menu onClick={onClick} selectedKeys={[current]} mode="horizontal" items={items} />; }; export default Navbar;
以上代码使用了 antd 的 Menu 组件和 Icon 组件,使用浏览器访问 http://localhost:3000/test,显示内容如下:
Home Login # 菜单
Test Page
Footer