MAP-NEXT.JS
环境支持
Nodejs 10.13或更高版本
基础
Pages
在Nextjs中,一个page就是一个页面组件,作为路由
支持动态路由:pages/ports/[id].js
预渲染:默认情况下Nextjs将预渲染每个page,意味着Nextjs会预先为每个页面生成html文件,而不是由客户端javascript来完成,预渲染可以带来更好的性能和SEO优化,每个生成的html文件都该与页面所需的最少的javascript代码关联,当浏览器加载一个page时,javascript代码将运行并使页面完全具有交互性,这个过程称为hydration
预渲染方式
静态生成(Static Generation)和服务端渲染(Server side Rendering),不同点是page生成的HTML的时机
- 静态生成:HTML在构建时生成,并在每次请求页面时重用
- 服务器端渲染:在每次页面请求时重新生成HTML
Nextjs允许为每个页面选择预渲染的方式,可以创建一个混合渲染的Nextjs应用程序,对于大多数页面使用静态生成,对小部分页面使用服务器端渲染
CND可以在没有额外配置的情况下缓存静态生成的页面以提高性能,但是某些情况下直能使用服务器端渲染,静态生成性能更高
可以将客户端渲染与静态生成或服务器端渲染一起使用,意味着页面的部分可以完全由客户端javascript呈现
再一个页面中使用静态生成,在构建时将生成此页面对应的html文件,这意味着在生产环境中,运行next build时将生成页面对应的html文件,此HTML文件将在每个页面请求中被重用也可以被CDN缓存
生成不带数据的静态页面
页面渲染不需要任何外部数据,在这种情况下只需要在构建时为每个页面生成一个html文件
需要获取数据的静态生成
某些页面需要获取外部数据以进行预渲染
- 页面内容取决于外部数据:使用getStaticProps
- 页面paths取决于外部数据使用getStaticPaths(通常还需要同时使用getStaticProps)
function Blog({posts}){
return (
<ul>
{
posts.map((post)=>{
<li>{{post.title}}</li>
})
}
</ul>
)
}
//此函数在构建时被调用
export async function getStaticProps(){
//调用外部API
const res = await fetch('https://www.xxx.com/posts')
const posts = await res.json()
//通过返回{props: {posts}}对象
//在构建时接收到posts参数
return {
props:{
posts,
}
}
}
export default Blog
要在预渲染时获取此数据,Nextjs允许从同一文件export导出一个名为getStaticProps的async异步函数,该函数在构建时被调用,并允许在与渲染时将获取的数据传给props作为参数传递给页面
页面路径取决于外部数据
Next.js 允许创建具有 动态路由的页面,pages/posts/[id].js
构建id所对应的内容时可能需要从外部获取数据,预渲染的路径取决于外部数据,,Nextjs允许从动态页面pages/posts/[id].js中导出一个名为getStaticPaths的异步函数,该函数在构建时调用,并允许你指定要预渲染的路径
同样在pages/posts/[id].js ,你还需要导出getStaticProps以便获取id所对应的博客文章的数据进行预渲染
//此函数在构建时被调用
function Post({post}){
//Render post
}
export async function getStaticPaths(){
//调用外部API获取博文列表
const res = await fetch('https://,,,/posts')
const posts = awawi res.json()
//根据博文列表生成所需要的预渲染路径
const paths = posts.map((posts) => `/posts/${post.id}`)
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false }
}
//构建时也会被调用
export async function getStaticProps({params}){
//params 包含id
//如果路由是/posts/1 那么params.id=1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
//通过props参数向页面传递博文的数据
return {
props:{
post
}
}
}
export default Post
什么时候应该使用静态生成
尽可能使用静态生成(带有或不带数据),因为所有的page都是可以构建一次并托管到CDN上,这比让服务器根据每个页面请求来渲染页面块的多
- 营销页面
- 博客文章
- 电商商品列表
- 帮助和文档
在用户请请求之前预先渲染页面,如果无法再用户请求之前预渲染页面,则不适合使用静态生成,因为页面显示频繁更新的数据,并且页面内容也会随着请求变化
建议
- 将静态生成与客户端渲染一起使用,可以跳过页面某些部分的预渲染,然后使用客户端javascript来动态生成这些内容
- 使用服务器端渲染:nextjs针对每个页面的请求预渲染,由于CDN无法缓存该页面,因此速度会比较慢,但是预渲染的页面将使用都是最新的
服务器端渲染
如果页面使用服务器端渲染,则会在每次页面请求时重新生成页面的html文件,要对page使用服务器端渲染需要export一个名为getServerSideProps的异步函数,服务器将在也每次页面请求时调用此函数
如果页面需要预渲染频繁更新数据,可以编写getServerProps获取该数据传并传递给page
function Page({data}){
//Render data
}
export async function getServerSideProps(){
const res = await fetch(`https://.../data`)
const data = await res.json()
return {
props:{
data
}
}
}
export default Page
getServerSideProps 类似于 getStaticProps,但两者的区别在于 getServerSideProps 在每次页面请求时都会运行,而在构建时不运行
- 静态生成(推荐): HTML 是在 构建时(build time) 生成的,并重用于每个页面请求。要使页面使用“静态生成”,只需导出(export)页面组件或导出(export) getStaticProps 函数(如果需要还可以导出 getStaticPaths 函数)。对于可以在用户请求之前预先渲染的页面来说,这非常有用。你也可以将其与客户端渲染一起使用以便引入其他数据。
- 服务器端渲染: HTML 是在 每个页面请求 时生成的。要设置某个页面使用服务器端渲染,请导出(export) getServerSideProps 函数。由于服务器端渲染会导致性能比“静态生成”慢,因此仅在绝对必要时才使用此功能。
获取数据
获取数据以进行预渲染的三种方式
- getStaticProps 静态生成 获取数据在构建时
- getStaticPaths 静态生成 指定动态路由以根据数据进行预渲染
- getServerSideProps 服务器端渲染,每次请求都会调用该函数
getStaticProps
如果在页面中导出了异步函数getStaticProps,每次构建的时候都会调用此函数以获取数据并传递给page的props中
export async function getStaticProps(context){
return {
props: {}//传递给页面组件
}
}
context包含以下属性
- params:包含使用动态路由的页面的路由参数,如果页面名称为[id].js,则参数将类似于{id: }
- preview:页面处于预览模式
- previewData:包含由setPreviewData设置的预览数据。
- locale:包含活动的语言环境(如果启用)。
- locales:包含所有受支持的语言环境(如果启用)。
- defaultLocale:包含配置的默认语言环境(如果启用)。
返回对象包含
- props:页面组件将接收的props,该数据必须存在。 应该是一个可序列化的对象
- revalidate:可选的时间(以秒为单位),之后可以重新生成页面
- notFound:一个可选的布尔值,允许页面返回404状态和页面
export async function getStaticProps(context){
const res = await fetch(`https://.../data`)
const data = await res.json()
if(!data){
return {
notFound: true
}
}
return {
props:{}
}
}
当设置fallback:false时,不需要notFound对象,
- redirect:允许重定向到内部和外部资源,当设置重定向时需要设置{ destination: string, permanent: boolean },在极少数情况下,可能需要为旧的HTTP客户端分配自定义状态代码,以正确重定向。 在这些情况下,可以使用statusCode属性代替永久属性,但不能同时使用两者
export async function getStaicProps(context){
const res = await fetch(`https://...`)
const data = await res.json()
if(!data){
return {
redirct:{
destination: '/',
permanent: false,
}
}
}
return {
props:{}
}
}
不允许在构建时进行重定向,如果在构建时已经知道重定向,则应将其添加到next.config.js中
可以导入模块中使用top-level scope 在getStaticProps中使用,getStaticProps使用的导入不会和客户端绑定在一起,可以在getStaticProps中编写服务器端代码,例如从文件系统或数据库中读取
不应该在应用程序中使用fetch来调用路由API,而是直接导入API路由并自己调用其函数
什么时候使用getStaticProps
- 页面所需的数据可以在构建时,用户请求之前获取到
- 数据来自无头CMD
- 数据可以被公共缓存(不是特定于某个用户)
- 页面必须预渲染,getStaticProps会生成HTML和JSON文件,CND可以将它们都缓存以提高性能
Typescript中使用getStaticProps
在next或中区getStaticProps的类型
import {GetStaticProps} from 'next'
export const getStaticProps:GetStaticProps = async (context) => {
//...
}
如果想要使用类型推断在props中
import { InferGetStaticPropsType } from 'next'
etpe Post = {
author: string
context: string
}
export const getStaticProps = async () => {
const res =await fetch('http://.../posts')
const posts:Post[] = await res.json()
return {
props:{
posts
}
}
}
增量静态生成
使用getStaticProps,不必停止依赖动态内容,因为静态内容也可以是动态的,增量静态云系通过流量进入时再后台重新渲染现有页面来更新现在页面
后台重新生成可确保始终从静态存储中不间断地为客户端提供服务,并且仅在完成生成后才会推送新建页面
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
//在构建时在服务器端调用此函数。
//如果没有服务器,则可以再次调用它
//已启用重新验证,并且有新请求
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js将尝试重新生成页面:
//-收到请求时
//-每秒最多一次
revalidate: 1, // In seconds
}
}
博客列表将没交重新验证一次,如果有新添加的博客文章,几乎立即可用,而无需重新构建应用程序或进行新的部署
大规模静态内容 与传统SSR不同,增量静态再生可确保保留今天的好处
- 延迟没有峰值,页面持续快速投放
- 页面永远不会脱机,如果后台页面重新生成失败,则就业面保持不变
- 低数据库和后端负载,页面最多被重新计算一次
使用process.cwd()读取文件
需要获取文件的完整路径
由于Nextjs将代码编译到一个单独的目录中,所以不能使用__dirname,因为它返回的路径和pages路径不同
可以使用process.cwd()可以获取到执行Nextjs的目录
import fs from 'fs'
import path from 'path'
function Blog({posts}){
return (
<ul>
{
posts.map(
(post) => (
<li>
<h3>{post.filename}</h3>
<p>{post.content}</p>
</li>
)
)
}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps(){
const postsDirectory = path.join(process.cwd(), 'posts')
const filenames = fs.readdirSync(postsDirectory)
const posts = filenames.map((filename) => {
const filePath = path.join(postsDirectory, filename)
const fileContents = fs.readFileSync(filePath, 'utf-8')
//通常需要解析和转换内容
//例如在这里可以将markdown转换为html
return {
filename,
content:fileContents
}
})
return {
props:{
posts
}
}
}
export default Blog
技术细节
仅在构建时运行
由于getStaticProps在构建时运行,因此它不会生成仅在请求时间内可用的数据,例如查询参数或HTTP标头,因为它会生成静态HTML。
直接编写服务器端代码
getStaticProps仅在服务器端运行。 它永远不会在客户端上运行。 它甚至不会包含在浏览器的JS捆绑包中。 这意味着可以编写代码,例如直接数据库查询,而无需将其发送到浏览器。 不应从getStaticProps获取API路由-而是可以直接在getStaticProps中编写服务器端代码。
静态生成HTML和JSON
在构建时预渲染带有getStaticProps的页面时,除了页面HTML文件之外,Next.js还会生成一个JSON文件,其中包含运行getStaticProps的结果。
该JSON文件将用于通过next / link(文档)或next / router(文档)的客户端路由。 当导航到使用getStaticProps预呈现的页面时,Next.js会获取此JSON文件(在构建时进行预先计算),并将其用作页面组件的props。 这意味着客户端页面转换不会调用getStaticProps,因为仅使用导出的JSON。
仅在页面中导出
getStaticProps只能从页面导出。 无法从非页面文件中导出它。 出现此限制的原因之一是,在呈现页面之前,React需要拥有所有必需的数据。 必须使用导出异步函数getStaticProps(){}-如果将getStaticProps添加为页面组件的属性,则该函数将不起作用。
根据开发中的每个请求运行
每次请求时调用getStaticProps
Preview Mode
在某些情况下,可能希望暂时绕过Static Generation,并在请求时而不是在构建时呈现页面。 例如,可能正在使用无头CMS,并且想要在草稿发布之前对其进行预览。
Next.js通过称为“预览模式”的功能支持此用例
getStaticPaths (Static Generation)
如果页面具有动态路由(文档)并使用getStaticProps,则它需要定义在构建时必须呈现为HTML的路径列表。
如果从使用动态路由的页面中导出名为getStaticPaths的异步函数,则Next.js将静态预呈现getStaticPaths指定的所有路径。
The paths key (required)
路径key确定将要预渲染的路径。 例如,假设您有一个使用动态路由的页面,该页面名为pages / posts / [id] .js。 如果从此页面导出getStaticPaths并为路径返回以下内容:
return {
paths: [
{params:{id: '1'}},
{params:{id: '2'}}
],
fallback: ...
}
Next.js将使用pages / posts / [id] .js中的页面组件在构建时静态生成posts / 1和posts / 2。
每个参数的值必须与页面名称中使用的参数匹配:
- 如果页面名称是pages / posts / [postId] / [commentId],则参数应包含postId和commentId。
- 如果页面名称使用包罗万象的路由,例如pages / [... slug],则参数应包含slug(数组)。 例如,如果此数组为['foo','bar'],则Next.js将在/ foo / bar静态生成页面。
- 如果页面使用可选的全部路由,请提供null,[],undefined或false来呈现最根目录的路由。 例如,如果您为页面/[[........slug]]提供slug:false,则Next.js将静态生成页面/。
The fallback key (required)
getStaticPaths返回的对象必须包含一个布尔fallback。
fallback: false
如果fallback为false,则getStaticPaths未返回的任何路径都将产生404页面。 如果有少量要预渲染的路径,则可以执行此操作-因此它们都是在构建时静态生成的。 当不经常添加新页面时,它也很有用。 如果向数据源添加了更多项目并需要呈现新页面,则需要再次运行该构建。
fallback: true
如果fallback为true,则getStaticProps的行为将更改:
从getStaticPaths返回的路径将在构建时由getStaticProps呈现为HTML。 在构建时尚未生成的路径将不会产生404页面。相反,Next.js会在对此类路径的第一个请求时提供页面的“后备”版本。 在后台,Next.js将静态生成请求的路径HTML和JSON。这包括运行getStaticProps。 完成后,浏览器将接收生成路径的JSON。这将用于自动呈现带有所需道具的页面。从用户的角度来看,该页面将从备用页面切换到整页。 同时,Next.js将此路径添加到预渲染页面列表中。对相同路径的后续请求将为生成的页面提供服务,就像在构建时预渲染的其他页面一样。 fallback:使用下一个导出时不支持true。
Fallback pages
使用router可以检测到是否正在渲染,router.isFallback为true
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
fallback:如果您的应用程序具有大量依赖于数据的静态页面(请考虑:一个非常大的电子商务网站),则true很有用。 您想预渲染所有产品页面,但是构建将永远花费。
取而代之的是,您可以静态地生成一小部分页面并使用后备广告:其余部分为true。 当有人请求尚未生成的页面时,用户将看到带有加载指示器的页面。 此后不久,getStaticProps完成,并且页面将使用请求的数据呈现。 从现在开始,请求同一页面的每个人都将获得静态预渲染的页面。
这样可确保用户在保持快速构建的同时始终获得快速体验,并获得“静态生成”的好处。
fallback:true不会更新生成的页面,为此请查看“增量静态再生”。
fallback: 'blocking'
如果后备是“阻止”,则getStaticPaths未返回的新路径将等待生成与SSR相同的HTML(因此为什么会阻止),然后将其缓存以供将来使用,因此每个路径仅发生一次。
getStaticProps的行为如下:
从getStaticPaths返回的路径将在构建时由getStaticProps呈现为HTML。 在构建时尚未生成的路径将不会产生404页面。相反,Next.js将对第一个请求进行SSR并返回生成的HTML。 完成后,浏览器将接收生成路径的HTML。从用户的角度来看,它将从“浏览器正在请求页面”过渡到“已加载整个页面”。没有加载/后备状态的闪烁。 同时,Next.js将此路径添加到预渲染页面列表中。对相同路径的后续请求将为生成的页面提供服务,就像在构建时预渲染的其他页面一样。 后备:默认情况下,“阻止”不会更新生成的页面。要更新生成的页面,请结合使用增量静态再生和后备:“阻止”。
==如果您要静态地预先渲染使用动态路由的页面,则应使用getStaticPaths。==
Typescript 使用GetStaticPaths
import { GetStaticPaths } from 'next'
export const getStaticPaths: GetStaticPaths = async () => {
// ...
}
在具有动态路由参数的页面上使用getStaticProps时,必须使用getStaticPaths。
不能将getStaticPaths与getServerSideProps一起使用。
getStaticPaths只能从页面导出。 无法从非页面文件中导出它。
另外,您必须使用导出异步函数getStaticPaths(){}-如果将getStaticPaths添加为页面组件的属性,则该函数将不起作用。
getServerSideProps (Server-side Rendering)
如果从页面导出名为getServerSideProps的异步函数,则Next.js将使用getServerSideProps返回的数据针对每个请求预渲染此页面。
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
参数包含如下:
- params:如果此页面使用动态路线,则参数包含路线参数。如果页面名称为[id] .js,则参数将类似于{id:...}。
- req:HTTP IncomingMessage对象。
- res:HTTP响应对象。
- query:查询字符串。
- preview:如果页面处于预览模式,则预览为true,否则为false。请参阅预览模式文档。
- previewData:由setPreviewData设置的预览数据
- resoolvedUrl:请求URL的规范化版本,它为客户端转换去除_next / data前缀,并包括原始查询值。
- local:语言环境包含活动的语言环境(如果启用)。
- locales:语言环境包含所有支持的语言环境(如果启用)。
- defaultLocale:包含配置的默认语言环境(如果启用)。
- getServerSideProps:应该返回带有以下内容的对象:
getServerSideProps返回对象
- 页面props将接收的带有props的必需对象。它应该是一个可序列化的对象
- notFound-一个可选的布尔值,允许页面返回404状态和页面
- redirect-可选的重定向值,允许重定向到内部和外部资源。 它应与{destination:string,permanent:boolean}的形状匹配。 在极少数情况下,可能需要为旧的HTTP客户端分配自定义状态代码,以正确重定向。 在这些情况下,可以使用statusCode属性代替永久属性,但不能同时使用两者。 以下是其工作方式的示例:
export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: {}, // will be passed to the page component as props
}
}
仅在需要预渲染必须在请求时获取其数据的页面时,才应使用getServerSideProps。 first byte (TTFB)的时间将比getStaticProps慢,这是因为服务器必须在每个请求上计算结果,并且如果没有额外的配置,CDN不能缓存结果。
TypeScript: Use GetServerSideProps
import { GetServerSideProps } from 'next'
export const getServerSideProps: GetServerSideProps = async (context) => {
// ...
}
//props的类型推断
import { InferGetServerSidePropsType } from 'next'
type Data = { ... }
export const getServerSideProps = async () => {
const res = await fetch('https://.../data')
const data: Data = await res.json()
return {
props: {
data,
},
}
}
function Page({ data }: InferGetServerSidePropsType<typeof getServerSideProps>) {
// will resolve posts to type Data
}
export default Page
直接请求此页面时,getServerSideProps将在请求时间运行,并且此页面将使用返回的props进行预渲染。 当通过next / link(文档)或next / router(文档)在客户端页面转换上请求此页面时,Next.js将API请求发送到服务器,该服务器运行getServerSideProps。 它将返回包含运行getServerSideProps的结果的JSON,并且该JSON将用于呈现页面。 Next.js将自动处理所有这些工作,因此,只要定义了getServerSideProps,您就不需要做任何其他事情。
如果页面包含经常更新的数据,并且不需要预先呈现数据,则可以在客户端获取数据。 一个示例是特定于用户的数据。 运作方式如下:
首先,立即显示没有数据的页面。 可以使用“静态生成”来预渲染页面的某些部分。 您可以显示丢失数据的加载状态。 然后,在客户端获取数据,并在准备好时显示它们。 例如,此方法适用于用户仪表板页面。 由于信息中心是一个私有的,特定于用户的页面,因此SEO无关紧要,并且该页面无需预先呈现。 数据经常更新,这需要获取请求时数据。
Next.js背后的团队已经创建了一个名为SWR的React hook来进行数据获取。 如果您要在客户端获取数据,我们强烈建议您这样做。 它处理缓存,重新验证,焦点跟踪,间隔重新获取等等。 :
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetch)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
内置对CSS的支持
Next.js 允许你在 JavaScript 文件中导入(import) CSS 文件。 因为 Next.js 扩展了 JavaScript 中的 import 的概念,才能让这一功能成为可能。
添加全局样式表
添加全局样式表到应用程序中,在pages/_app.js文件中导入CSS文件,例如
body{
padding: 20px 20px 60px;
max-width: 600px;
margin: 0 auto;
}
//_ap.js文件不存在可以手动创建
import '../style.css'
export default function MyApp({Componnt, pageProps}){
return <Component {...pageProps}/>
}
由于样式表的全局特性
全局引入的样式在页面和组件中都会生效,全局样式只应该在_app.js中引入
编辑样式时热重载,在生产环境中所有的css文件将自动合并为一个精简的.css文件
import styles from node_module
从nextjs 9.5.4开始,可以在node_modules引入css,在应用的任何文件中
类似于bootstrap和nprogress,应该在_app.js中引入文件
//pages/_app.js
import 'bootstrap/dist/css/bootstrap.css'
export default function MyApp({Component, pageProps}){
return <Component {...pageProps}/>
}
导入第三方的css到组件中
//components/ExampleDialog.js
import {useState} from 'react'
import {Dialog} from '@reach/dialog'
import '@reach/dialogstyless.css'
function ExampleDialog(props){
const [showDialog, setShowDialog] = useState(false)
const open = () => setShowDialog(true)
const close = () => setShowDialog(false)
return (
<div>
<button onClick={open}>Open Dialog</button>
<Dialog isOpen={showDialog} onDismiss={close}>
<button className="close-button" onClick={close}>
<VisuallyHidden>Close</VisuallyHidden>
<span aria-hidden>×</span>
</button>
<p>Hello there. I am a dialog</p>
</Dialog>
</div>
)
}
添加组件级CSS
Nextjs通过[name].module.css文件命名约定来支持CSS模块
CSS模块通过自动创建唯一的类名从而将CSS限定在局部范围内,所以可以在不同的文件中使用相同的CSS类名,而不担心冲突
CSS模块称为包含组件级CSS的理想方法,CSS模块文件可以导入到应用程序的额任何位置
//components/Button.module.css
.error{
color: white;
background-color: red;
}
//coponent/Button.js
import styles from './Button.module.css'
export function Button(){
return (
<button
type="button"
// Note how the "error" class is accessed as a property on the imported
// `styles` object.
className={styles.error}
>
Destroy
</button>
)
}
Sass支持
Nextjs支持导入.sass和.scss扩展名的文件,可以通过CSS模块及.module.scss或.module.sass扩展名来使用组件级的Sass
要使用Nextjs内置的Sass支持前需要安装sass
npm install sass
对Sass的支持与内置的CSS具有相同的好处和限制
自定义Sass参数
可以在next.config.js文件中的sassOptions参数中配置Sass
const path = require('path')
module.exports = {
sassOptions: {
includePaths: [path.join(__dirname, 'styles')]
}
}
Less和Stylus的支持
- @zeit/next-less
- @zeit/next-stylus
CSS-in-js
支持使用任何现有的css-in-js解决方案,最简单的方案是内联样式
支持shadow CSS但是不支持服务器端渲染
图像组件和图像优化
10.0.0版开始,Next.js具有内置的图像组件和自动图像优化功能。
Next.js图像组件next / image是HTML <img>元素的扩展,它是为现代Web演变而来的。
Automatic优化允许浏览器支持时以WebP之类的现代格式调整大小,优化并提供图像。这样可以避免将大图像传送到具有较小视口的设备。它还允许Next.js自动采用未来的图像格式,并将其提供给支持这些格式的浏览器。
Automatic优化可与任何图像源一起使用。即使映像由外部数据源(例如CMS)托管,也可以对其进行优化。
Next.js无需在构建时优化图像,而是根据用户要求按需优化图像。与静态站点生成器和纯静态解决方案不同,无论发布10张图像还是1000万张图像,构建时间都不会增加。
图像默认是延迟加载的。这意味着不会因为视口外的图像而影响页面速度。图像在滚动到视口时加载。
始终以避免避免Cumulative Layout Shift的方式渲染图像,Cumulative Layout Shift是Google将在搜索排名中使用的核心网络要素。
Image Component
应用程序中添加image组件
import Image from 'next/image'
function Home(){
return (
<>
<h1>My Homepage</h1>
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
/>
<p>Welcome to my homepage!</p>
</>
)
}
export default Home
可以在next.config.js中配置image的高级技巧
要为外部网站上托管的图像启用图像优化,需要对image src使用绝对路径,并指定允许优化的域,确保不滥用外部网址
module.exports = {
images: {
domains: ['example.com'],
},
}
如果要使用云提供程序来优化映像,而不是使用Next.js的内置映像优化,则可以配置加载程序和路径前缀。 这使您可以为Image src使用相对URL,并自动为提供程序生成正确的绝对URL。
module.exports = {
images: {
loader: 'imgix',
path: 'https://example.com/myaccount/',
},
}
支持以下图像优化云提供商:
- Vercel: Works automatically when you deploy on Vercel, no configuration necessary. Learn more
- Imgix: loader: 'imgix'
- Cloudinary: loader: 'cloudinary'
- Akamai: loader: 'akamai'
- Default: Works automatically with next dev, next start, or a custom server
下面介绍默认加载程序的缓存算法。 对于所有其他加载程序,请参考您的云提供商的文档。
图像可根据请求进行动态优化,并存储在<distDir> / cache / images目录中。 优化的图像文件将用于后续请求,直到达到到期为止。 当发出与缓存但过期的文件匹配的请求时,将在生成新的优化映像并缓存新文件之前删除该缓存文件。
到期时间(或“最长期限”)由上游服务器的Cache-Control标头定义。
如果在Cache-Control中找到s-maxage,则使用它。 如果未找到s-maxage,则使用max-age。 如果未找到最大寿命,则使用60秒。
您可以配置deviceSizes和imageSizes以减少可能生成的图像总数。
以下配置用于高级用例,通常不是必需的。 如果选择配置以下属性,则将来的更新中将覆盖对Next.js默认值的所有更改。
设备尺寸
在某些情况下,如果您从网站用户那里知道预期的设备宽度,则可以使用deviceSizes属性指定设备宽度断点的列表。 当next / image组件使用layout =“ responding”或layout =“ fill”时,将使用这些宽度,以便为访问您的网站的设备提供正确的图像。
如果未提供任何配置,则使用下面的默认值。
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
},
}
图片尺寸
您可以使用imageSizes属性指定图像宽度的列表。 这些宽度应该与deviceSizes中定义的宽度不同(通常更小),因为数组将被串联在一起。 当next / image组件使用layout =“ fixed”或layout =“ intrinsic”时,将使用这些宽度。
如果未提供任何配置,则使用下面的默认值。
module.exports = {
images: {
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
}
静态文件服务
Next.js 支持将静态文件(例如图片)存放到根目录下的 public 目录中,并对外提供访问。public 目录下存放的静态文件的对外访问路径以 (/) 作为起始路径。
如果你添加了一张图片到 public/me.png 路径,则以下代码就能访问到此图片:
import Image from 'next/image'
function Avatar() {
return <Image src="/me.png" alt="me" width="64" height="64" />
}
export default Avatar
此文件夹还可用于存放 robots.txt、favicon.ico、Google 站点验证文件以及任何其它静态文件(包括 .html 文件)!
请勿为 public 改名。此名称是写死的,不能修改,并且只有此目录能过够存放静态资源并对外提供访问。
请确保静态文件中没有与 pages/ 目录下的文件重名的,否则这将导致错误。
只有放在 public 目录下的静态资源才能在 构建时 由 Next.js 找到。在运行时添加的文件不可用。建议使用第三方服务,例如 AWS S3 来持久地存储文件。
快速刷新
快速刷新是Next.js的功能,可为您提供对React组件所做的编辑的即时反馈。 默认情况下,所有9.4或更高版本的Next.js应用程序均启用“快速刷新”。 启用Next.js快速刷新后,大多数编辑应该在一秒钟内可见,而不会丢失组件状态。
如果您编辑仅导出React组件的文件,则Fast Refresh将仅更新该文件的代码,然后重新渲染您的组件。您可以编辑该文件中的任何内容,包括样式,渲染逻辑,事件处理程序或效果。 如果您使用不是React组件的导出来编辑文件,则Fast Refresh将重新运行该文件以及导入该文件的其他文件。因此,如果Button.js和Modal.js都导入theme.js,则编辑theme.js将同时更新这两个组件。 最后,如果您编辑由React树之外的文件导入的文件,则Fast Refresh将退回到完全重新加载的状态。您可能有一个呈现React组件但还导出非反应组件导入的值的文件。例如,也许您的组件还导出了一个常数,而非React实用程序文件将其导入。在这种情况下,请考虑将常量迁移到单独的文件中,然后将其导入两个文件中。这将重新启用“快速刷新”功能。其他情况通常可以用类似的方式解决。
容错处理
语法错误 如果在开发过程中发生语法错误,则可以对其进行修复并再次保存该文件。 该错误将自动消失,因此您无需重新加载该应用程序。 您将不会丢失组件状态。
运行时错误 如果您犯了一个导致组件内部运行时错误的错误,那么您将获得一个上下文覆盖。 修正错误后,系统会自动关闭该叠加层,而无需重新加载该应用程序。
如果在渲染过程中未发生错误,则组件状态将保留。 如果在渲染过程中确实发生了错误,React将使用更新的代码重新安装您的应用程序。
如果您的应用程序中存在错误边界(这是生产中正常失败的一个好主意),则它们将在出现渲染错误后在下一次编辑时重试渲染。 这意味着具有错误边界可以防止您始终重置为根应用程序状态。 但是,请记住,错误边界不应太细粒度。 它们由React在生产中使用,并且应始终经过精心设计。
局限性 快速刷新会尝试在您正在编辑的组件中保留本地React状态,但前提是必须这样做。 您可能会在每次编辑文件时看到重置本地状态的一些原因:
对于类组件,不保留局部状态(仅函数组件和Hooks保留状态)。 您正在编辑的文件可能除了React组件之外还具有其他导出功能。 有时,文件会导出调用诸如HOC(WrappedComponent)之类的高阶组件的结果。 如果返回的组件是类,则状态将被重置。 匿名箭头功能,例如export default()=>
; 导致快速刷新不保留本地组件状态。 对于大型代码库,您可以使用我们的名称-default-component codemod。 随着更多代码库移至功能组件和Hook,可以期望在更多情况下保留状态。快速刷新默认情况下会在函数组件(和Hooks)中保留React局部状态。 有时,您可能需要强制重置状态并重新安装组件。 例如,如果要调整仅在安装时发生的动画,这可能很方便。 为此,您可以在要编辑的文件中的任意位置添加// @refresh reset。 该指令是文件本地的,并指示“快速刷新”在每次编辑时重新装入该文件中定义的组件。 您可以放置console.log或调试器; 在开发过程中编辑的组件中。
快速刷新和hooks 如果可能,“快速刷新”会尝试在两次编辑之间保留组件的状态。特别是,useState和useRef会保留其先前的值,只要您不更改其参数或Hook调用的顺序即可。
具有依赖关系的挂钩(例如useEffect,useMemo和useCallback)将始终在“快速刷新”期间更新。在进行快速刷新时,将忽略其依赖项列表。
例如,当您将useMemo(()=> x 2 [x])编辑为useMemo(()=> x 10,[x])时,即使x(依赖项)没有改变了。如果React没有这样做,那么您的编辑将不会在屏幕上反映出来!
有时,这可能会导致意外结果。例如,即使具有空依赖项数组的useEffect仍将在快速刷新期间重新运行一次。
但是,即使没有快速刷新,编写可偶尔重新运行useEffect的代码也是一个好习惯。它将使您以后更容易引入新的依赖关系,并且由React Strict Mode强制实施,我们强烈建议启用它。
typescript
项目的根目录中创建一个空的tsconfig.json文件:
Next.js将自动使用默认值配置该文件。 还支持为您自己的tsconfig.json提供自定义编译器选项。
Next.js使用Babel处理TypeScript,它有一些警告,并且某些编译器选项的处理方式有所不同。
然后,运行next(通常为npm run dev或yarn dev),Next.js将指导您完成所需软件包的安装以完成设置:
将在项目的根目录中创建一个名为next-env.d.ts的文件。 该文件确保TypeScript编译器选择Next.js类型。 您无法删除它,但是可以对其进行编辑(但不需要这样做)。
默认情况下,TypeScript严格模式是关闭的。 如果您对TypeScript感到满意,建议在tsconfig.json中将其打开。
默认情况下,Next.js将在下一个版本中进行类型检查。 我们建议在开发过程中使用代码编辑器类型检查。
如果要使错误报告静音,请参阅文档以忽略TypeScript错误。
静态生成和服务器端渲染
For getStaticProps, getStaticPaths, and getServerSideProps, you can use the GetStaticProps, GetStaticPaths, and GetServerSideProps types respectively:
import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next'
export const getStaticProps: GetStaticProps = async (context) => {
// ...
}
export const getStaticPaths: GetStaticPaths = async () => {
// ...
}
export const getServerSideProps: GetServerSideProps = async (context) => {
// ...
}
import type { NextApiRequest, NextApiResponse } from 'next'
export default (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).json({ name: 'John Doe' })
}
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default (req: NextApiRequest, res: NextApiResponse<Data>) => {
res.status(200).json({ name: 'John Doe' })
}
如果您有自定义应用程序,则可以使用内置类型AppProps并将文件名更改为./pages/_app.tsx,如下所示:
// import App from "next/app";
import type { AppProps /*, AppContext */ } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext: AppContext) => {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
// return { ...appProps }
// }
export default MyApp
路径别名和baseUrl
Next.js自动支持tsconfig.json的“路径”和“ baseUrl”选项。
环境变量
- 使用.env.local加载环境变量
- 将环境变量公开给浏览器
加载环境变量
Next.js内置支持将环境变量从.env.local加载到process.env中。
//.env.local
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
// pages/index.js
export async function getStaticProps() {
const db = await myDB.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS,
})
// ...
}
Next.js将自动在.env *文件中扩展变量($ VAR)。
# .env
HOSTNAME=localhost
PORT=8080
HOST=http://$HOSTNAME:$PORT
# .env
A=abc
WRONG=pre$A # becomes "preabc"
CORRECT=pre\$A # becomes "pre$A"
向浏览器公开环境变量
默认情况下,所有通过.env.local加载的环境变量仅在Node.js环境中可用,这意味着它们不会公开给浏览器。
为了向浏览器显示变量,您必须在变量前加上NEXTPUBLIC。 例如:
NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
这会自动将process.env.NEXTPUBLIC_ANALYTICS_ID加载到Node.js环境中。 允许您在代码中的任何地方使用它。 由于NEXT_PUBLIC前缀,该值将内联到发送给浏览器的JavaScript中。
// pages/index.js
import setupAnalyticsService from '../lib/my-analytics-service'
// NEXT_PUBLIC_ANALYTICS_ID can be used here as it's prefixed by NEXT_PUBLIC_
setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)
function HomePage() {
return <h1>Hello World</h1>
}
export default HomePage
默认环境变量
通常,只需要一个.env.local文件。 但是,有时您可能想为开发(下一个开发)或生产(下一个启动)环境添加一些默认值。
Next.js允许您在.env(所有环境)、. env.development(开发环境)和.env.production(生产环境)中设置默认值。
.env.local始终会覆盖默认设置。
Vercel上的环境变量
在Vercel上进行部署时,可以在Vercel仪表板的项目的“环境变量”部分中配置机密。
您仍然可以使用.env,.env.development和.env.production添加默认值。
如果已经配置了开发环境变量,则可以使用以下命令将它们拉入.env.local以便在本地计算机上使用:
vercel env pull .env.local
测试环境变量
除开发和生产环境外,还有第三个选项:测试。 以相同的方式可以为开发或生产环境设置默认值,也可以对.env.test文件进行相同的操作以测试环境(尽管此文件与前两个文件并不常见)。
当使用诸如jest或cypress之类的工具运行测试时,此功能非常有用,您只需要出于测试目的而设置特定的环境变量。 如果将NODE_ENV设置为测试,则将加载测试默认值,尽管您通常不需要手动执行此操作,因为测试工具会为您解决。
测试环境与开发和生产环境之间存在细微的差异,您需要牢记:.env.local不会加载,因为您希望测试对每个人产生相同的结果。 这样,每个测试执行将通过忽略您的.env.local(旨在覆盖默认设置)在不同的执行中使用相同的env默认值。
Supported Browsers and Features
Next.js支持IE11和所有现代浏览器(Edge,Firefox,Chrome,Safari,Opera等),无需进行任何配置。
Polyfills
我们透明地注入IE11兼容性所需的polyfill。 此外,我们还注入了广泛使用的polyfill,包括:
fetch()—替换:whatwg-fetch和unfetch。 URL —替换:URL软件包(Node.js API)。 Object.assign()—替换:object-assign,object.assign和core-js / object / assign。 如果您的任何依赖项都包含这些polyfill,则会自动将其从生产版本中删除,以避免重复。
另外,为了减小包的大小,Next.js将只为需要它们的浏览器加载这些polyfill。 全球大多数网络流量不会下载这些polyfill。
服务器端polyfill
除了客户端上的fetch()之外,Next.js在Node.js环境中还填充了fetch()。 您可以在服务器代码(例如getStaticProps)上使用fetch(),而无需使用polymorphs(例如isomorphic-unfetch或node-fetch)。
自定义polyfill
如果您自己的代码或任何外部npm依赖项需要目标浏览器不支持的功能,则需要自己添加polyfills。
在这种情况下,应在“自定义<App>”或单个组件中为所需的特定polyfill添加顶级导入。
JavaScript Language Features
Next.js允许您直接使用最新的JavaScript功能。 除ES6功能外,Next.js还支持:
- Async/await (ES2017)
- Object Rest/Spread Properties (ES2018)
- Dynamic import() (ES2020)
- Optional Chaining (ES2020)
- Nullish Coalescing (ES2020)
- Class Fields and Static Properties (part of stage 3 proposal)
and more!
路由
Next.js具有基于页面概念的基于文件系统的路由器。
将文件添加到页面目录后,它会自动作为路由使用。
pages目录中的文件可用于定义最常见的模式
路由索引
路由器将自动将名为index的文件路由到目录的根目录。
- pages/index.js → /
- pages/blog/index.js → /blog
嵌套路由
路由器支持嵌套文件。 如果创建嵌套的文件夹结构,则文件将仍然以相同的方式自动路由。
- pages/blog/first-post.js → /blog/first-post
- pages/dashboard/settings/username.js → /dashboard/settings/username
动态路由
要匹配动态细分,您可以使用方括号语法。 这使您可以匹配命名参数。
- pages/blog/[slug].js → /blog/:slug (/blog/hello-world)
- pages/[username]/settings.js → /:username/settings (/foo/settings)
- pages/post/[...all].js → /post/* (/post/2020/id/title)
页面之间的链接
Next.js路由器允许您在页面之间进行客户端路由转换,类似于单页面应用程序。
提供了一个称为Link的React组件来执行此客户端路由转换。
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/about">
<a>About Us</a>
</Link>
</li>
<li>
<Link href="/blog/hello-world">
<a>Blog Post</a>
</Link>
</li>
</ul>
)
}
export default Home
在上面的示例中,我们有多个链接,每个链接都将路径(href)映射到已知页面:
- / → pages/index.js
- /about → pages/about.js
- /blog/hello-world → pages/blog/[slug].js
链接到动态路由
可以使用插值来创建路径,这对于动态路径段非常方便。 例如,要显示已作为props传递到组件的帖子列表:
import Link from 'next/link'
function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${encodeURIComponent(post.slug)}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
)
}
export default Posts
或者,使用URL对象:
import Link from 'next/link'
function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link
href={{
pathname: '/blog/[slug]',
query: { slug: post.slug },
}}
>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
)
}
export default Posts
现在,我们不使用插值来创建路径,而是在href中使用URL对象,其中:
pathname是页面目录中页面的名称。 / blog / [slug]在这种情况下。 查询是具有动态细分的对象。 在这种情况下。
注入路由
要访问React组件中的路由器对象,可以使用useRouter或withRouter。
通常,我们建议使用useRouter。
动态路由
对于复杂的应用程序,使用预定义的路径定义路由并不总是足够的。 在Next.js中,您可以在页面([param])中添加方括号以创建动态路由(也称为url链接,漂亮的url等)。
参考以下页面pages / post / [pid] .js:
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
何路由(如/ post / 1,/ post / abc等)都将与pages / post / [pid] .js匹配。 匹配的path参数将作为查询参数发送到页面,并将与其他查询参数合并。
例如,路由/ post / abc将具有以下查询对象:
{ "pid": "abc" }
同样,路由/ post / abc?foo = bar将具有以下查询对象:
{ "foo": "bar", "pid": "abc" }
但是,路由参数将覆盖具有相同名称的查询参数。 例如,路由/ post / abc?pid = 123将具有以下查询对象:
{ "pid": "abc" }
多个动态路由段以相同的方式工作。 页面pages / post / [pid] / [comment] .js将匹配路由/ post / abc / a-comment,其查询对象为:
{ "pid": "abc", "comment": "a-comment" }
客户端到动态路由的导航是通过next / link处理的。 如果我们想要链接到上面使用的路由,它将看起来像这样:
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link href="/post/abc">
<a>Go to pages/post/[pid].js</a>
</Link>
</li>
<li>
<Link href="/post/abc?foo=bar">
<a>Also goes to pages/post/[pid].js</a>
</Link>
</li>
<li>
<Link href="/post/abc/a-comment">
<a>Go to pages/post/[pid]/[comment].js</a>
</Link>
</li>
</ul>
)
}
export default Home
捕获所有的路由
通过在括号内添加三个点(...),可以扩展动态路线以捕获所有路径。 例如:
- pages/post/[...slug].js matches /post/a, but also /post/a/b, /post/a/b/c and so on.
匹配的参数将作为查询参数(在示例中为Slug)发送到页面,并且始终是数组,因此,路径/ post / a将具有以下查询对象:
{ "slug": ["a"] }
对于/ post / a / b以及任何其他匹配路径,新参数将添加到数组中,如下所示:
{ "slug": ["a", "b"] }
选配所有路由
通过在双括号([[... slug]])中包含参数,可以使捕获所有路由成为可选操作。
例如,pages / post / [[... slug]]。js将匹配/ post,/ post / a,/ post / a / b等。
捕获所有路由和可选捕获所有路由之间的主要区别在于,使用可选时,不带参数的路由也会匹配(在上例中为/ post)。
查询对象如下:
{ } // GET `/post` (empty object)
{ "slug": ["a"] } // `GET /post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /post/a/b` (multi-element array)
注意事项
预定义路由优先于动态路由,动态路由优先于所有路由。 看下面的例子:
pages / post / create.js-将匹配/ post / create
pages / post / [pid] .js-将匹配/ post / 1,/ post / abc等,但不匹配/ post / create
pages / post / [... slug] .js-将匹配/ post / 1/2,/ post / a / b / c等,但不匹配/ post / create,/ post / abc
通过自动静态优化进行静态优化的页面将被水化,而不会提供其路由参数,即查询将是一个空对象({})。
After hydration Next.js将触发对您的应用程序的更新,以在查询对象中提供路由参数。
浅路由
浅路由允许您更改URL,而无需再次运行数据获取方法,包括getServerSideProps,getStaticProps和getInitialProps。
您将通过路由器对象(由useRouter或withRouter添加)接收更新的路径名和查询,而不会丢失状态。
要启用浅层路由,请将shallow选项设置为true。 参考以下示例:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Current URL is '/'
function Page() {
const router = useRouter()
useEffect(() => {
// Always do navigations after the first render
router.push('/?counter=10', undefined, { shallow: true })
}, [])
useEffect(() => {
// The counter changed!
}, [router.query.counter])
}
export default Page
该URL将更新为/?counter = 10。 并且页面不会被替换,仅更改路由的状态。
您还可以通过componentDidUpdate监视URL的更改,如下所示:
componentDidUpdate(prevProps) {
const { pathname, query } = this.props.router
// verify props have changed to avoid an infinite loop
if (query.counter !== prevProps.router.query.counter) {
// fetch data based on the new query
}
}
注意事项
浅路由仅适用于相同页面URL更改。 例如,假设我们还有一个名为pages / about.js的页面,您可以运行以下页面:
router.push('/?counter=10', '/about?counter=10', { shallow: true })
由于这是一个新页面,因此即使我们要求执行浅层路由,它也会卸载当前页面,加载新页面并等待数据提取。
API路由
API 路由为使用 Next.js 构建自己的 API 提供了一种简单的解决方案。
pages/api 目录下的任何文件都将作为 API 端点映射到 /api/*,而不是 page。这些文件只会增加服务端文件包的体积,而不会增加客户端文件包的大小。
例如,以下 API 路由 pages/api/user.js 处理 json 响应:
export default function handler(req, res) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ name: 'John Doe' }))
}
为了使 API 路由能正常工作,你需要导出(export)一个默认函数(即 请求处理器),然后该函数将接收到以下参数:
- req: 一个 http.IncomingMessage 实例,以及一些预建的中间件,可以在 此处 查看
- res: 一个 http.ServerResponse 实例,以及一些辅助函数,可以在 [此处]](/docs/api-routes/response-helpers.md) 查看
注意事项
- API 路由 未指定 CORS 标头意味着它们在默认情况下 仅是同源(same-origin)策略。你可以通过使用 cors 中间件 包装出一个请求处理器来自定义此行为。
- API路由不能与 next export 一起使用
动态 API 路由
API 路由支持 [动态路由]](/docs/routing/dynamic-routes.md),并与 pages 一样遵循相同的文件命名规则。
例如,API 路由 pages/api/post/[pid].js 的实现代码如下:
export default function handler(req, res) {
const {
query: { pid },
} = req
res.end(`Post: ${pid}`)
}
现在,发往 /api/post/abc 的请求将收到响应: Post: abc。
索引路由(Index routes)和动态 API 路由
一个通常的 RESTful 模式将设置以下路由:
GET api/posts - 获取 post 列表,可能还有分页 GET api/posts/12345 - 获取 id 为 12345 的 post 我们可以按照如下两种方式建模:
方案 1:
- /api/posts.js
- /api/posts/[postId].js 方案 2:
- /api/posts/index.js
- /api/posts/[postId].js 以上两种方案是等效的。还有一个仅使用 /api/posts/[postId].js 的第三种方式,由于动态路由 (including Catch-all routes - see below) 没有 undefined 状态,而 GET api/posts 在任何情况下都不匹配 /api/posts/[postId].js,因此这种方式是无效的。
捕获所有 API 的路由
通过在方括号内添加三个英文句点 (...) 即可将 API 路由扩展为能够捕获所有路径的路由。例如:
- pages/api/post/[...slug].js 匹配 /api/post/a,也匹配 /api/post/a/b、/api/post/a/b/c 等。
- slug 并不是必须使用的,你也可以使用 [...param]
匹配的参数将作为查询参数(本例中的 slug )传递给 page(页面),并且始终是数组格式,因此,路经 /api/post/a 将拥有以下query 对象:
{ "slug": ["a"] }
对于 /api/post/a/b 路径,以及任何其他匹配的路径,新参数将被添加到数组中,如下所示:
{ "slug": ["a", "b"] }
API 路由 pages/api/post/[...slug].js 将是如下所示:
export default function handler(req, res) {
const {
query: { slug },
} = req
res.end(`Post: ${slug.join(', ')}`)
}
现在,一个发送到 /api/post/a/b/c 的请求将收到 Post: a, b, c 响应。
可选的捕获所有 API 的路由
通过将参数放在双方括号中 ([[...slug]]) 可以使所有路径成为可选路径。
例如, pages/api/post/[[...slug]].js 将匹配 /api/post、/api/post/a、/api/post/a/b 等。
The main difference between catch all and optional catch all routes is that with optional, the route without the parameter is also matched (/api/post in the example above).
该 query 对象如下所示:
{ } // GET `/api/post` (empty object)
{ "slug": ["a"] } // `GET /api/post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /api/post/a/b` (multi-element array)
注意事项
- 预定义的 API 路由优先于动态 API 路由,而动态 API 路由优先于捕获所有 API 的路由。看下面的例子:
- pages/api/post/create.js - 将匹配 /api/post/create
- pages/api/post/[pid].js - 将匹配 /api/post/1, /api/post/abc 等,但不匹配 /api/post/create
- pages/api/post/[...slug].js - 将匹配 /api/post//1/2, /api/post/a/b/c 等,但不匹配 /api/post/create、/api/post/abc
API 中间件
API 路由提供了内置的中间件,用于解析传入的请求 (req)。这些中间件是:
- req.cookies - An object containing the cookies sent by the request. Defaults to {}
- req.query - An object containing the query string. Defaults to {}
- req.body - An object containing the body parsed by content-type, or null if no body was sent
自定义配置
每个 API 路由都可以导出(export)一个用以更改默认配置 config 对象,如下所示:
export const config = {
api: {
bodyParser: {
sizeLimit: '1mb',
},
},
}
该 api 对象包括可用于 API 路由的所有配置。
bodyParser 用于正文解析,如果你希望以 Stream 形式接收数据,可将将其禁用,如下所示:
export const config = {
api: {
bodyParser: false,
},
}
bodyParser.sizeLimit 代表的是可以解析的数据负载的的最大体积,可以采用 bytes 所支持的任何格式,例如:
export const config = {
api: {
bodyParser: {
sizeLimit: '500kb',
},
},
}
externalResolver 是一个显式标志,用以告诉服务器此路由正在由外部解析器(例如 express 或 connect)处理。启用此参数将禁用针对未处理的 request(请求)所发出的警告。
export const config = {
api: {
externalResolver: true,
},
}
针对 Connect/Express 中间件的支持
你还可以使用 Connect 兼容的中间件。
例如,可以利用 cors 软件包为 API 端点 配置 CORS 。
首先来安装 cors:
npm i cors
# or
yarn add cors
将 cors 添加到 API 路由中:
import Cors from 'cors'
// Initializing the cors middleware
const cors = Cors({
methods: ['GET', 'HEAD'],
})
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
async function handler(req, res) {
// Run the middleware
await runMiddleware(req, res, cors)
// Rest of the API logic
res.json({ message: 'Hello Everyone!' })
}
export default handler
使用TypeScript扩展req / res对象
为了获得更好的类型安全性,不建议扩展req和res对象。 而是使用纯函数与它们一起使用:
// utils/cookies.ts
import { serialize, CookieSerializeOptions } from 'cookie'
import { NextApiResponse } from 'next'
/**
* This sets `cookie` using the `res` object
*/
export const setCookie = (
res: NextApiResponse,
name: string,
value: unknown,
options: CookieSerializeOptions = {}
) => {
const stringValue =
typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value)
if ('maxAge' in options) {
options.expires = new Date(Date.now() + options.maxAge)
options.maxAge /= 1000
}
res.setHeader('Set-Cookie', serialize(name, String(stringValue), options))
}
// pages/api/cookies.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { setCookie } from '../../utils/cookies'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
// Calling our pure function using the `res` object, it will add the `set-cookie` header
setCookie(res, 'Next.js', 'api-middleware!')
// Return the `set-cookie` header so we can display it in the browser and show that it works!
res.end(res.getHeader('Set-Cookie'))
}
export default handler
如果无法避免扩展这些对象,则必须创建自己的类型以包括额外的属性:
// pages/api/foo.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { withFoo } from 'external-lib-foo'
type NextApiRequestWithFoo = NextApiRequest & {
foo: (bar: string) => void
}
const handler = (req: NextApiRequestWithFoo, res: NextApiResponse) => {
req.foo('bar') // we can now use `req.foo` without type errors
res.end('ok')
}
export default withFoo(handler)
请记住,这是不安全的,因为即使您从导出中删除withFoo(),代码仍会编译。
Response Helpers
响应 (res) 包含一组类似于 Express.js 的方法以改善开发人员的体验并提高创建新 API 端点的速度,请看以下示例:\
export default function handler(req, res) {
res.status(200).json({ name: 'Next.js' })
}
包含的帮助手函数如下:
- res.status(code) - 这是一个设置状态码的函数。code 必须是有效的 HTTP 状态码
- res.json(json) - 发送 JSON 格式的响应。json 必须是有效的 JSON 对象
- res.send(body) - 发送 HTTP 响应。body 的类型可以是 string、 object 或 Buffer
- res.redirect([status,] path) - Redirects to a specified path or URL. status must be a valid HTTP status code. If not specified, status defaults to "307" "Temporary redirect".
高级特性
萌萌萌:
class Scheduler {
constructor() {
this.waitingQueue = [], // 待运行的任务
this.runningQueue = [] // 正在运行的任务
}
add(promiseCreator) {
return new Promise((resolve, reject) => {
promiseCreator.resolve = resolve
if (this.runningQueue.length < 2) {
this.runTask(promiseCreator)
} else {
this.waitingQueue.push(promiseCreator)
}
})
}
runTask(promiseCreator) {
this.runningQueue.push(promiseCreator);
promiseCreator().then(res => {
promiseCreator.resolve(res);
this.removeTask(promiseCreator);
if(this.waitingQueue.length > 0) {
this.runTask(this.waitingQueue.shift());
}
});
}
removeTask(promiseCreator) {
let index = this.runningQueue.findIndex(promiseCreator)
this.runningQueue.splice(index, 1)
}
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order))
}
addTask(1000, 1)
addTask(500, 2)
addTask(300, 3)
addTask(400, 4)