文章目录
- 前言
- 关键点
- 目录结构
- 基本概念
- 服务端组件和客户端组件
- 路由
- Page
- Layout
- 后端交互
- SWR
- 构建部署
- SSR部署
- 部署到nginx
- nginx只代理静态资源
- FAQ
前言
随着All in React战略实施,公司逐步将前端、移动端转移到React技术栈上来。nextjs和nuxtjs有很多异曲同工之处。
关键点
- 关键概念:服务端组件(RSC, React Server Component),客户端组件, SWR, ‘use client’。要区分哪些代码会在build time期间执行,哪些在run time期间执行。哪些在服务端执行,也就是在Node上执行,哪些在浏览器里执行。
- 路由问题:你如果想静态导出(static export, 通常用于Nginx上部署),就得舍弃动态路由,就是舍弃app/[…folder]/xxx这种形式的页面
- Next.js 通过直观地集成中间件和后端功能而脱颖而出。它是一个一站式解决方案。开发者可以在同一个框架内处理前端和后端,简化整个流程。这种统一消除了不断的上下文切换,甚至编程语言切换的需要,确保了高效的开发体验。
目录结构
注意:next13和之前版本的差异,主要是app route 和 pages route 的区别。而且,app和pages目录可以共存。
Next.js 13 introduced the new App Router with new features and conventions. The new Router is available in the app directory and co-exists with the pages directory.
典型工程结构:
├── app
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── ui
│ │ ├── alert-dialog.tsx
│ │ ├── button.tsx
│ │ ├── dropdown-menu.tsx
│ │ └── ...
│ ├── main-nav.tsx
│ ├── page-header.tsx
│ └── ...
├── lib
│ └── utils.ts
├── styles
│ └── globals.css
├── next.config.js
├── package.json
├── postcss.config.js
├── tailwind.config.js
└── tsconfig.json
几个基本目录:
- app: app router,缺省存放的组件都是服务端组件, Next.js 13 推荐的目录
- pages:页面路由,客户端组件。Next.js 12以及之前版本推荐的目录
- public:存放静态资源
- src: 可选,可以将其它组件,或者app, pages都放到src
如何将老版本中pages下页面迁移到新版本的app下,可以参加指引
通过引入 app/directory (beta) 来改进 Next.js 中的路由和布局,这是之前为征求社区反馈而发布的 Layouts RFC 的结果。包括对以下内容的支持:
- Layouts:轻松共享 UI,同时保留状态并避免重新渲染。
- Server Components:使服务器优先成为大多数动态应用程序的默认设置。
- Streaming:显示即时加载状态并流式传输更新。
- Suspense for Data Fetching:新的 usehook 支持 component-level fetching。
一些约束:
- 当你路由到/demo时,src/pages/demo/index.tsx和src/app/demo/page.tsx都会被匹配到,所以这两个文件不能同时存在。事实上,目前版本的nextjs中,pages和app这两个目录一般不会同时存在。
- 如果你定义了src/app/demo/abc.tsx,你没法通过/demo/abc路由到它,但如果你定义src/pages/demo/abc.tsx就可以。
- app/layout.tsx是一个特殊的文件,定义了整个项目的布局。
- pages/_app.tsx也是一个特殊文件,能自定义App对象
入口文件:src/pages/_app.js, src/app/layout.txs
基本概念
服务端组件和客户端组件
一个UI组件可以在服务端渲染,也可以在客户端渲染。UI组件如果在服务端进行渲染和缓存,就称为服务端组件。反之,称为客户端组件。用"use client"指令可以申明为客户端组件。
所谓”渲染“,就是把React组件转为html的过程。和html在浏览器中绘制的渲染不同。
Next.js 提供了三种主要的渲染策略,每种都对 SEO 和性能有独特的影响:
- 静态渲染:路由会在构建时进行渲染,或在数据重新验证后在后台运行。在构建时创建页面,缓存并通过内容交付网络(CDN)进行分发。由于它的可预测性和快速的加载时间,它非常适合不经常更改的内容,比如博客文章。NextJS v13 的服务端组件默认是静态渲染。
- 动态渲染:页面根据用户请求生成,促进个性化内容。虽然这提供了自定义用户体验,但速度可能会慢一些,可能会影响 SEO。不过,通过适当的调整,仍然可以保持最佳性能。
- 流式渲染:服务器逐渐渲染 UI 片段。这种方法可以增强感知性能,允许用户在整个内容完成渲染之前与页面的部分部分进行互动——这对依赖较慢数据获取的内容来说绝对是一个优势。
路由
分为App route和Page route。对于App route而言,只有目录下的page.js 和 route.js能被公开访问到。
路由规则:
- 下划线开头的文件夹是私有文件夹,不会被路由到
- 中括号括起来的文件夹是动态路由。App route中,动态路由只能用于文件夹,pages route中可以用于文件。
- 小括号括起来的是route groups,例如:(auth)/register , (auth)/login , (auth)/forgot-password 可以被/register, /login, /forgot-password访问到,路由路径中不需要加auth。
页面 | 路由 |
---|---|
blog/[slug]/page.js | /blog/post-1 |
路由切换可以使用<Link>组件,也可以使用useRouter()。注意:next/navigation和next/router下提供的useRouter()的不同:
The useRouter from next/router is to be used in the pages folder, the initial way of setting up routes in Next.js. Since v13, they introduced a new directory called app (used when you say Yes to the last question shown in the below image), built on top of Server Components, where you define routes differently and use useRouter from next/navigation
Page
Layout
布局定义页面结构,可以在多个页面之间共享。
- root layout : 放在app下面,只有根布局能包含<html>和<body>标签
后端交互
服务端采用内置的fetch()函数,客户端采用route handler获取数据。
SWR
SWR在这里指的是 “stale-while-revalidate”,
const fetcher = async (url) => await axios.get(url).then((res) => res.data);
const { data, error } = useSWR(address, fetcher);
构建部署
三种渲染方式对应三种部署形式。
-
CSR:客户端发起ajax请求,获得json数据后在浏览器渲染。这种方式通常部署到nginx。当然用node也可以。
-
SSG: 全静态页面,渲染在构建期间就完成。也是部署在nginx上。
-
SSR: 在服务端渲染,服务端获取数据,然后在服务端就生成好对应的html,再返回给浏览器。只能部署在node上。
-
官方推荐使用 Vercel 来完成一键自动化构建部署
-
SSG 适用于网站不依赖于动态数据的场景,例如你的博客网站,如果博文数量固定,内容在数据库中,你可以采用SSG方式生成一个全静态的站点,等价于导出成一本电子书。
-
SSR适用场景更多。
Next.js 13 中包含 Turbopack —— Webpack 的新的基于 Rust 的继任者。在 Next.js 12 中,开始过渡到 native Rust 驱动的工具。首先从 Babel 迁移,这导致转译速度提高了 17 倍。然后替换了 Terser,这使得 minification 提高了 6 倍。据说,更新速度比 Webpack 快 700 倍,比 Vite 快 10 倍。Turbopack 对服务器组件、TypeScript、JSX、CSS 等提供了开箱即用的支持。不过在 Alpha 版期间,许多功能尚不受支持。目前这个特性只在next dev中支持,next build中还有待时日。
可在 Next.js 13 中通过 next dev --turbo 试用 Turbopack alpha。Turbopack 还是出自 Webpack 作者 Tobias Koppers 之手。Tobias Koppers 于 2021 年 4 月加入 Vercel,参与了 Turbopack 的开发
SSR部署
Next.js使用一个Node.js HTTP服务器来提供SSR。
服务端渲染例子:
// pgaes/blog/[slug].tsx
import React, { ReactElement } from 'react'
export default function Post({ post }): ReactElement {
return (
<>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{
__html:post.content
}}></div>
</>
)
}
export async function getServerSideProps(context) {
const { slug }=context.params
const res = await fetch(`https://.../api/post/${slug}`)
const post = await res.json()
return {
props: {
post
},
}
}
getServerSideProps() 是在node端处理,每个 request 请求时执行。因为文章内容写完之后是通常不变的,所以可以先将页面静态存储在服务器上,这样就可以大大减小数据库压力。
getStaticProps 在构建时请求数据:
export async function getStaticProps(context) {
// fetch data
return {
props: {
//data
},
}
}
这样构建时就生成了静态页面。在构建时获取全部文章列表。
getStaticPaths 构建时获取动态路由的数据。例如博客详情页是一个动态路由,就需要 getStaticPaths 这个API。
export async function async getStaticPaths() {
const slugs= await getAllSlugs()
return {
paths: slugs.map(slug=>({
params:slug
})),
fallback: true //or false
};
}
例如:
// pages/posts/[slug].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// 如果页面还没静态生成,则先显示下面的loading
// 直到 `getStaticProps()`运行完成
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// 在构建时运行,获取全部文章路径
export async function getStaticPaths() {
return {
// 在打包时值生成 `/posts/1` 和 `/posts/2` 的静态页面
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// 开启其他页面的静态生成
// For example: `/posts/3`
fallback: true,
}
}
// 在构建时运行,根据params中的id 获取文章详情
export async function getStaticProps({ params }) {
// 如果页面的路由是 /posts/1, 这 params.id 的值就是1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// 把数据专递给页面的props
return {
props: { post },
//当请求进入的时候再次生成文章详情页,比如修改文章重新生成
// 1s 内最多生成1次
revalidate: 1,
}
}
export default Post
构建命令:
yarn build
yarn start --port 8080
部署到nginx
先要配置 next.config.js 的 basePath 字段:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
basePath: '/cms',
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
// !! WARN !!
// Dangerously allow production builds to successfully complete even if
// your project has type errors.
// !! WARN !!
ignoreBuildErrors: true,
},
output: 'export',
}
module.exports = nextConfig
然后执行yarn build,生成的文件会输出到out目录下。一锅端到nginx上:
server {
listen 80;
server_name localhost;
# 二级路径
location /cms {
rewrite ^/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:3000;
}
}
注意:静态部署到nginx的限制:
生成静态页面时,p.tsx会生成p.html,如果部署到nginx上,<Link href=‘/p’> 就会找不到页面,所以可以添加配置:
const nextConfig = {
trailingSlash: true,
...
或者采用rewrite机制:
rewrites: async () => {
return [
{
source: '/auth/logon.html',
destination: '/auth/logon',
},
]
},
nginx只代理静态资源
在生产环境中,也可以采用nginx + Node结合的部署方案,让nginx只代理静态资源,其它SSR渲染部分交给Node。
server {
listen 80;
server_name next-app;
root /app;
index .next/server/pages/index.html;
autoindex off;
# nginx代理static目录,减小对node服务的压力
location ~* /_next/static/.*(js|css|png|jpg|jpeg|svg|gif|ico)$ {
rewrite /_next/(.*) /.next/$1 break;
try_files $uri $uri/;
expires 7d;
add_header Cache-Control "public";
gzip on;
gzip_types text/plain text/css image/svg+xml image/png application/javascript text/xml application/xml application/xml+rss text/javascript;
}
location ~* /assets/.*(png|jpg|jpeg|svg|gif)$ {
root /app/public;
expires 7d;
add_header Cache-Control "public";
gzip on;
gzip_types text/plain text/css image/svg+xml image/png application/javascript text/xml application/xml application/xml+rss text/javascript;
}
location /favicon.ico {
root /app/public;
expires 7d;
add_header Cache-Control "public";
gzip on;
gzip_types image/x-icon;
}
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
FAQ
- 采用src后,页面找不到,可以删除.next,重新运行。
- next13和ant design集成后,出现错误:
SyntaxError: Cannot use import statement outside a module
解决办法,在next.config.js里添加:
transpilePackages: [
'rc-util',
'@ant-design',
'antd',
'rc-pagination',
'rc-picker'
],
后来引入ahooks也是同样的报错,加上就没事了。