Next.js学习笔记
环境搭建
npx create-next-app@latest,会自动为你设置所有内容,下载没有下载的依赖
可以加上 --typescript来使用typescript
使用npm run dev来启动开发环境服务器,自动运行在3000端口
项目目录结构&初步认知
其中pages是存放具体页面的地方
目前感受到的一个好处是pages自动配置了路由
比如这里我写了一个about.jsx,我并不需要配置路由,nex.tjs直接配置了路由,我可以直接访问http://localhost:3000/about
同时next.js可以使用动态路由:pages/posts/[id].js => post/1 ,post/2 ...
同时next.js支持 静态文件服务,在public中的文件可以被直接访问到,比如我们可以直接访问 http://localhost:3000/favicon.ico
对于路由不匹配的情况,也自动进行了捕获和处理
静态渲染
next.js一个突出的优点就是它可以静态渲染页面:如果一个页面使用了 静态生成,在 构建时(build time) 将生成此页面对应的 HTML 文件 。这意味着在生产环境中,运行 next build 时将生成该页面对应的 HTML 文件。然后,此 HTML 文件将在每个页面请求时被重用,还可以被 CDN 缓存。
每个生成的 HTML 文件都与该页面所需的最少 JavaScript 代码相关联。当浏览器加载一个 page(页面)时,其 JavaScript 代码将运行并使页面完全具有交互性。(此过程称为 水合(hydration) 。)这可以带来更好的性能和 搜索引擎优化效果。
我的理解就是next.js预先将一个page,即一个react组件所需的所有资源(组件中的jsx代码、组件依赖的css样式、包括组件引入的其他custom hooks或者子组件)渲染成一个静态的html文件。这自然能够更好的提升性能:毕竟只需要一个html文件,比之前的网络请求减少太多了;同时搜索引擎等爬虫也更好找到,毕竟是静态的html,而不是动态的js。
那么这个静态的页面怎么样使用动态的数据(比如需要从服务器端获取的)甚至动态的路由呢(比如post/1)呢?
next.js提供了两个异步函数用来获取动态数据:**getStaticProps用于获取页面内容依赖的数据,而getStaticPaths**用来获取路由依赖的动态数据
我们可以通过官方的例子来看这两个函数的作用:假设现在有一个博客首页,里面需要从服务器端获取博客标题渲染成一个列表,同时需要点击具体的博客进入博客阅读页面。
我们有一个Blog组件,其中需要使用posts这个参数,它是由具体的博客组成的数组,而博客中有id和title两个属性。
我们用getStaticPaths向https://.../posts发送请求获得了posts数据,使用其中的id属性组成paths数组,这个数组就是动态路由依赖的数组。
而getStaticProps也是类似的获得了post数据并直接返回给了Blog组件进行使用。
在Blog组件中,我们使用map语法取出每一篇post,使用<Link>进行路由跳转,在路由跳转中使用动态路由:/blog/${encodeURIComponent(post.slug)}
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.id)}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
)
}
// 此函数在构建时被调用
export async function getStaticPaths() {
// 调用外部 API 获取博文列表
const res = await fetch('https://.../posts')
const posts = await res.json()
// 据博文列表生成所有需要预渲染的路径
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// 仅会在build时重新渲染paths
// { fallback: false } 表明未匹配到的路由将会返回404
// { fallback: 'blocking' } 表明会直接不处理(静默取消)未匹配到的路由
return { paths, fallback: false }
}
// 此函数在构建时被调用
export async function getStaticProps() {
// 调用外部 API 获取博文列表
const res = await fetch('https://.../posts')
const posts = await res.json()
// 通过返回 { props: { posts } } 对象,Blog 组件
// 在构建时将接收到 `posts` 参数
return {
props: {
posts,
},
}
}
export default Blog
而在具体的博客页面,我们可以使用router.query来获取路由的信息并使用,比如在post/[id]/index.js中,可能有这样的组件结构:
我们用useRouter这个next.js提供的custom hook中的router中的router.query获得query参数,即路由参数,并用es6的解构语法提取出id渲染在页面中。
import { useRouter } from 'next/router'
import Link from 'next/link'
const Post = () => {
const router = useRouter()
const { id } = router.query
return (
...
<h1>Post: {id}</h1>
...
)
}
export default Post
getStaticPorps 进阶操作
动态更新
如果须要动态更新已经渲染好的经静态页面,可以用revaliate来表明更新的频率:
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js 会尝试在每10秒为新的请求重新生成页面
revalidate: 10, // 单位是秒
}
}
读取文件
有些时候我们需要从静态文件中读取数据,比如静态博客(直接上传md文件即可生成页面,比如hexo等),这时我们就需要在getStaticProps中去获取静态文件中的数据,这时我们可以使用fs这个库
import { promises as fs } from 'fs'
import path from 'path'
...
export async function getStaticProps() {
const postsDirectory = path.join(process.cwd(), 'posts')
const filenames = await fs.readdir(postsDirectory)
const posts = filenames.map(async (filename) => {
const filePath = path.join(postsDirectory, filename)
const fileContents = await fs.readFile(filePath, 'utf8')
// 通常还需要进一步转换这些fileContents,比如md转HTMl
return {
filename,
content: fileContents,
}
})
return {
props: {
posts: await Promise.all(posts),
},
}
}
getStaticPaths 进阶操作
除了直接匹配单个动态路由,我们还可以匹配多个动态路由、不同层级的路由甚至兜底的路由。
多个动态路由
考虑pages/posts/[id].js匹配到posts/1、posts/2等,自然会想到是否可以更进一步匹配某个动态路由下的动态路由呢?答案当然是可以的,比如我们可以使用pages/posts/[postId]/[commentId]去匹配posts/1/2等,但是这时params应该同时包含postId 和commentId
...
const paths = posts.map((post) => ({
params: {
postId: post.id,
commentId: post.comment.id
},
}))
return { paths, fallback: false }
不同层级的路由
有时候我们并不知道需要匹配多少层级,比如上面的例子:我们匹配了文章,还匹配了文章下的评论。我们可以用slug来实现同时匹配多层层级的需求
slug意为鼻涕虫,想象它拖在地上缓慢行走的样子,就能理解为什么是同时匹配多层,我个人喜欢理解成“惰性”
必须我们可以使用pages/[...slug]来接收所有路由参数,同时我们需要在params中包含一个slug数组。比如当slug数组是['foo', 'bar']时,Next.js会静态生成/foo/bar
兜底路由
如果使用假值作为路由参数,会自动被/捕获,实现兜底。假值包括null,[],undefined,false,比如说在slug数组中如果为空或者直接设置slug:false,那么就会返回首页,即/
定制错误页面
Next.js可以非常方便的定制错误页面,只需要在pages文件夹下新建[状态码名].js即可,比如我们可以新建pages/404.js来定制状态码为404时的页面。
export default function Custom404() {
return <h1>定制404页面,同样也可以定制其他状态码如500</h1>
}
api路由
可以在pages/api下构建自己的api文件,这些文件只会在服务端文件包中存在,build之后是不会被包含的。
一个典型的api路由是这样的:
我们需要两个参数:
req用来接收:一个http.IncomingMessage实例,以及一些 预先构建的中间件,比如可以用req.method来判断请求方法。关于http.IncomingMessage的具体api可以参考amery2010.gitbooks.io/nodejs-api-…
而res用来接收一个http.ServerResponse实例,以及一些 辅助函数。比如这里使用的res.status和res.json
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
部署
Next.js 可以部署到任何支持 Node.js 的托管提供商处。确保你的 package.json 文件中设置了 package.json 文件中存在 "build" 和 "start" 两个构建命令。
执行 next build 命令将在 .next 文件夹中构建出用于生产环境的应用程序。构建之后,执行 next start 命令启动一个支持 混合页面(hybrid pages) 的 Node.js 服务程序,该服务程序将同时服务于静态生成的页面和服务器端渲染的页面。
而如果想要将 Next.js 应用程序导出为静态 HTML,可以使用命令next export来导出静态页面,页面会被导出到一个名为out的文件夹中