Next

什么是Nextjs

image-20220721135738322

Next.js 是一个流行的、轻量级的框架,用于配合 React 打造静态化和服务端渲染应用。 可以使用 React 构建您的 UI,然后逐步采用 Next.js 功能来解决常见的应用程序需求,例如路由、数据获取、集成 - 同时改善开发人员和最终用户的体验。

image-20220721135344697 image-20220721141812093 image-20220721141902581

Next 官网地址 托管在vercel平台上

常见服务端渲染框架

image-20220721141349158



常用的 CSR SSR SSG 几种渲染页面方式

CSR

CSR全称是 Client Side Rendering , 代表的是客户端渲染,渲染工作在客户端(浏览器)进行,比如Vue, React 等框架,都是先下载html (不是最终完整的html),然后下载js 来执行渲染出页面结果。

alt text

优点:

  1. 前后端分离。前端专注于界面开发,后端专注于api开发,且前端有更多的选择性,可以使用vue,react框架开发,而不需要遵循后端特定的模板。
  2. 服务器压力变轻了,渲染工作在客户端进行,服务器直接返回不加工的html
  3. 用户在后续访问操作体验好,(首屏渲染慢)可以将网站做成SPA,可以增量渲染

缺点:

  1. 不利于SEO,因为搜索引擎不执行JS相关操作,无法获取渲染后的最终html
  2. 首屏渲染时间比较长,因为需要页面执行ajax获取数据来渲染页面,如果请求接口多,不利于首屏渲染

SSR

SSR全称是 Server Side Rendering,代表的是服务端渲染。与客户端渲染不同的是,SSR输出的是一个渲染完成的html,整个渲染过程是在服务器端进行的。列如传统JSP, Node+Pug,PHP alt text

优点:

  1. 有利于SEO,由于页面在服务器生成,搜索引擎直接抓取到最终页面结果。
  2. 有利于首屏渲染,html所需要的数据都在服务器处理好,直接生成html,首屏渲染时间变短。

缺点:

  1. 占用服务器资源,渲染工作都在服务端渲染
  2. 用户体验不好,每次跳转到新页面都需要在重新服务端渲染整个页面,不能只渲染可变区域

SSG

SSG全称是 Static Site Generation ,代表的是静态站点生成。在构建的时候直接把结果页面输出html到磁盘,每次访问直接把html返回给客户端,相当于一个静态资源

alt text

优点:

  1. 减轻服务器压力,可以把生成的静态资源(html)放到CDN上,合理利用缓存
  2. 有利于SEO,由于html已经提前生成好,不需要服务端和客户端去渲染

缺点:

  1. 只适用于静态数据,对于经常改动的数据,需要每次重新生成页面。
  2. 用户体验不好,每次打开新页面都需要重新渲染整个页面,不能只渲染可变区域

ISR

Next.js 推出的 ISR(Incremental Static Regeneration) 方案, 允许在应用运行时再重新生成每个页面 HTML,而不需要重新构建整个应用。 这样即使有海量页面,也能使用上 SSG 的特性。一般来说,使用 ISR 需要 getStaticPathsgetStaticProps 同时配合使用

alt text

// pages/posts/[id].js
function Post(props) {
const { postData } = props;
return <div>{postData.title}</div>
}
export async function getStaticPaths() {
const paths = await fetch('https://.../posts');
return {
paths,
// 页面请求的降级策略,这里是指不降级,等待页面生成后再返回,类似于 SSR
fallback: 'blocking'
}
}
export async function getStaticProps({ params }) {
// 使用 params.id 获取对应的静态数据
const postData = await getPostData(params.id)
return {
props: {
postData
},
// 开启 ISR,最多每10s重新生成一次页面
revalidate: 10,
}
}

表示开启 ISR。在上面的例子中,指定 revalidate = 10,表示最多10秒内重新生成一次静态 HTML。当浏览器请求已在构建时渲染生成的页面时,首先返回的是缓存的 HTML,10s 后页面开始重新渲染,页面成功生成后,更新缓存,浏览器再次请求页面时就能拿到最新渲染的页面内容了。

Next.js 不仅支持 SSR、SSG、CSR、ISR,还支持渲染模式的混合使用。SSR + CSR(服务+客户), SSG + CSR(静态 + 客户),SSG + SSR (静态+服务)

安装

npx create-next-app@latest --ts

目录结构

|-- project
|-- .babelrc.js
|-- .eslintrc.json
|-- .gitignore
|-- README.md
|-- config.json
|-- next.config.js
|-- package.json
|-- server.js
|-- yarn.lock
|-- .vercel
| |-- README.txt
| |-- project.json
| |-- cache
|-- components # 组件目录
| |-- Hello
| |-- index.js
|-- pages # 页面目录
| |-- _app.js
| |-- index.js
| |-- index.module.less
| |-- 404
| | |-- index.js
| |-- _error
| | |-- index.jsx
| |-- api
| | |-- [id].js
| | |-- detail.js
| | |-- list.js
| |-- home
| | |-- home.module.css
| | |-- home.module.less
| | |-- index.js
|-- public
| |-- favicon.ico
| |-- vercel.svg
|-- static # 静态文件夹目录
| |-- favicon.ico
| |-- person.jpg
| |-- sum.png
| |-- vercel.svg
|-- styles # 全局样式目录
| |-- Home.module.css
| |-- globals.css
| |-- test.less
|-- unitl
|-- test.js


1.样式

全局样式

要将样式表添加到您的应用程序中,请在 pages/_app.js 文件中导入(import)CSS 文件。
// 根路径 所有page 都会进到这个页面
import '../styles/globals.css'
export default ({Component, pageProps}) => {
return <Component {...pageProps}/>
}

添加组件样式

Next.js 通过 [name].module.css 文件命名约定来支持 CSS 模块 。
import Link from "next/link"
import Styles from './home.module.css'
export default () => {
return <div className={Styles.home}>
<div>
<Link href="/posts/12" >
<a>link 跳转 id</a>
</Link>
<br/>
<Link href="/posts/12/12" >
<a>link 跳转 name</a>
</Link>
</div>
</div>
}

Css-in-js 内联样式

<Link href="/posts/12/12" >
<a style={{fontSize: "30px"}}>link 跳转 name</a>
</Link>

styled-jsx

styled-jsx 支持作用域隔离(isolated scoped)的 CSS
export default () => {
return <div className={Styles.home}>
<div>
<p>test home</p>
<style jsx>{`
p{
color: blue
}
`}</style>
<style global jsx>{`
html, body {
background: #ffffff
}
`}</style>
</div>
</div>
}

alt text

支持作用域隔离

less

cnpm i less less-loader next-with-less -S
next.config.js
const withLess = require("next-with-less");
module.exports = withLess({
lessLoaderOptions: {
},
})
# 全局引入
styles/test.less
import '../styles/globals.css'
import "../styles/test.less"
export default ({Component, pageProps}) => {
return <Component {...pageProps}/>
}
# 组件引入
home.module.less #[name].module.less
import Styless from './home.module.less'
export default () => {
return <div className={Styless.home}>
<div>
<p>test home</p>
</div>
</div>
}

2. 获取数据

nextjs 两种预渲染

服务端渲染

也被称为ssr 或者动态渲染

访问XXX路由之前,向服务器要数据,把要回来的数据和Html加工直接返回前台显示

每次页面请求(request)时 重新生成 HTML。

缺点: 当访问服务器人数多的情况下,服务器压力比较大。

静态化

访问XXX路由之前,向服务器要数据,把要回来的数据和Html加工生成真正的XXX.html文件

HTML 在 构建时 生成,并在每次页面请求(request)时重用。

优点: 下次访问同一个路由地址的时候,直接返回静态压面,减少服务器压力,提高性能

缺点: 就是页面数据不变的情况下,如果页面数据经常变化,还是需要服务端渲染。

以上三种获取数据方式 只能在page页面中调用……  如果想在组件中调用可以是使用next 提供的api 能力做一个转发

getServerSideProps

要对 page(页面)使用服务器端渲染,需要 export 一个名为 getServerSidePropsasync 函数。服务器将在每次页面请求时调用此函数。

import fetch from 'node-fetch'
// 页面
function CaseList({ data }) {
console.log(data, '用来接收http://10.60.104.23:3003/api/list')
return (<div>
has
</div>)
// Render data...
}
// node 层
export async function getServerSideProps(context) {
let res = await fetch('http://10.60.104.23:3003/api/list')
const { result: { data } } = await res.json()
console.log(data, 'res')
// 不存在
if (!data) {
return {
notFound: true,
}
}
// 通过props 传递参数到CaseList页面
return {
props: {data}, // will be passed to the page component as props
}
}
export default CaseList

getStaticProps

构建时获取数据。

// 静态页面
import fetch from "node-fetch"
export default function({data}) {
return <div>
details {JSON.stringify(data)}
</div>
}
export async function getStaticProps() {
let res = await fetch('http://10.60.104.23:3003/api/detail')
const { result: { data } } = await res.json()
console.log('details')
return {
props: {data},
}
}

getStaticPaths

pages/[id].js
// 动态页面
import {useRouter} from "next/router"
// 用于在使用动态路由时生成静态文件。
export async function getStaticPaths() {
let res = await fetch('http://10.60.104.23:3003/list')
const { result: { data } } = await res.json()
const paths = data.map((post) => ({
params: { id: post.id },
}))
console.log(data, 'getStaticPaths--->res')
return { paths: paths, fallback: "blocking" }
}
export async function getStaticProps({params: {id}}) {
console.log(id, 'params')
return {
props: {id}, // will be passed to the page component as props
}
}
export default function({id}) {
const router = useRouter()
return <div>
listData {id}
</div>
}

image

image

如你所见,getServerSideProps 类似于 getStaticProps,但两者的区别在于 getServerSideProps 在每次页面请求时都会运行,而在构建时不运行;
getStaticProps 是在构建的时候,获取数据生成静态页面.html和一个.json文件。

image

3. API 能力

API 路由为使用 Next.js 构建你自己的 API 提供了一种解决方案。
pages/api 目录下的任何文件都将作为 API 端点映射到 /api/*,而不是 page。 # 这些文件只会增加服务端文件包的体积,而不会增加客户端文件包的大小。

image

Api 路由都是在项目page/api 目录下

image

4. 图片

Nextjs 提供了一个next/image 组件

import Image from 'next/image'
# 引入图片
# 不支持 await import()或require()
import Person from '../../public/person.jpg'
# 在不知道图片具体大小的时候,可以设置父元素大小限制 子元素大小
<div style={{width: '800px', display:'inline-block'}}>
<Image src={Car} alt="Car" placeholder="blur" quality={1} layout="responsive"></Image>
</div>
<img src={"../../static/sum.png"} style={{ width: '800px' }} alt="" />
# 设置图片 宽高 占位用
# placeholder 有个模糊效果,预先加载渲染质量模块,等全部加载完了,在显示全部图片(对大图特别友好)
# 如果是要用到外部的图片需要配置 next.config.js 中配置 images domains
<div>
<Image src={Person} alt="beautiful of the person" layout="fixed" width={400} height={500} placeholder="blur" ></Image>
<Image src="http://si1.go2yd.com/get-image/0bVwH1EStDU" width={500} height={500}></Image>
</div>
images: {
domains: ['si1.go2yd.com'],
}

image

5. Script

Next.js 脚本组件next/script是 HTML<script>元素的扩展。它使开发人员可以在其应用程序中的任何位置设置第三方脚本的加载优先级,而无需直接附加到next/head,节省开发人员时间的同时提高加载性能。

import Script from "next/script"
<Script src="https://connect.facebook.net/en_US/sdk.js" strategy="beforeInteractive" onLoad={() => {
console.log('load ok')
}} onError={() => {console.log('err')}} />

image

beforeInteractive 在页面交互之前加载

beforeInteractive (默认): 页面变为交互式后立即加载

lazyOnload 在空闲时间加载

6. 自定义server.js

Nextjs 会带有自己的server, 如果你想自定义server, 需要确保所有情况都正常

const express = require('express')
const next = require('next')
console.log(process.env.PORT, '--->')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`)
})
})
package.json
"dev": "node server.js",

7. 自定义 _document.js

nextjs 本身自带了一套document ,如果想覆盖,如:

import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head>
<title>The First Project</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta name="author" content="lucky-泽" />
<meta name="description" content="lucky-泽-recode"></meta>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

Html, Head, Main, NextScrip 几个组件是必要的。而且在_document.js 获取数据几个方法(getServerSideProps, getStaticProps, getStaticPaths)。

8. 全局公共组件_app.js

// 根路径 所有page 都会先进到这个页面
import '../styles/globals.css'
import "../styles/test.less"
export default ({Component, pageProps}) => {
return <Component {...pageProps}/>
}

9. 自定义错误页面

image

10. 环境变量

.env.development (next dev) 环境执行时需要配置的;.env.production (next start) 生产环境需要配置的,这些配置的变量只能在node 环境中去执行,如果想在浏览器环境中获取到配置的变量需要 NEXT_PUBLIC_

image

image

11. 路由

nextjs 以page 页面作为路由文件,

动态路由

获取路由参数

import { useRouter } from 'next/router'
const router = useRouter()
const {query} = router

12. 重定向

重定向,您可以使用以下redirectsnext.config.js redirects是一个异步函数,它期望返回一个数组,其中包含具有sourcedestinationpermanent属性的对象:source是传入请求路径模式 destination是您要路由到的路径。permanent truefalse- 如果true将使用指示客户端/搜索引擎永久缓存重定向的 308 状态码,如果false将使用临时且未缓存的 307 状态码;

source / destination 路径支持 [通配符路径] [正则表达式路径] [标头、Cookie 和查询] basePath i18n api res.redirect() 重定向

image

image

13. 动态导入

Next.js 支持 JavaScript 的 ES2020动态import()

const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)

Nextjs 提供了next/dynamic 组件,支持动态导入组件,可以自定义加载文件和开启服务端渲染,类似 React18中的 React.lazy``<Suspense>``fallback

import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('@/components/dynamic_com'), {
loading: () => <p style={{fontSize: '20px'}}>加载中</p>, // 自定义加载文件
ssr: false // 是否需要服务端渲染(false 就是在浏览器中)
})
export default () => {
return (
<>
<p>测试动态组件</p>
<DynamicComponent />
<style jsx>{`
p{
display: block;
font-size: 20px !important;
}
`}</style>
</>
)
}

14. MDX

MDX 是 Markdown 的超集,可以直接在 Markdown 文件中编写 JSX,可以更加直观和快速的开发你的页面,由于 Markdown 本质上是静态内容,因此您无法根据用户交互性创建动态内容。MDX 的亮点在于它能够让您直接在标记中创建和使用 React 组件

npm install @next/mdx @mdx-js/loader

image!

如果你想让页面更加美观 还可以自定义组件

image!

15. 打包部署 vercel

npm run build #打包
npm run start #运行打包文件

docker 部署

# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# If using npm with a `package-lock.json` comment out above and use below instead
# COPY package.json package-lock.json ./
# RUN npm ci
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
docker build -t nextjs-docker .
docker run -p 3000:3000 nextjs-docker

vercle 托管

如果不会vercel 小伙伴 请自行学习 vercel

主要的流程是vercle 会绑定github/gitlab -> 导入 next 项目,vercel 会自动识别是哪个框架,会自动执行 install -> build->start 命令 完成部署之后会提供一个可以访问的地址,并且每次代码提交之后 会自动 构建 无需手动 构建。

SEO

参考链接 https://github.com/hexh250786313/Blog/issues/9