官方文档 Getting Started | Next.js (nextjs.org)
安装
npx create-next-app@latest --ts
# or
yarn create next-app --typescript
# or
pnpm create next-app --ts
运行
npm run dev
项目结构

| 文件 | 内容 | 
|---|---|
| pages | 页面文件 | 
| pages/api | api 数据接口 | 
| public | 静态资源文件 | 
| styles | 样式文件 | 
| next-env.d.ts | 确保 typescript 支持 | 
| next.config.ts | next 配置文件 | 
路由
nextjs 有一个基于页面概念的文件系统路由器,存放在 pages 下.js, .jsx, .ts, .tsx 文件都将作为组件,即文件路径 → 页面路由,例如这里的 index.tsx 映射为 index,pages/about.js 将映射为 /about。
同时还支持动态路由,创建pages/user/[id].tsx文件,然后访问user/1,user/2
import { useRouter } from 'next/router'
const User = () => {
  const router = useRouter()
  const { id } = router.query
  return <div>User id:{id} </div>
}
export default User
此时访问 http://localhost:3000/user/1 便可得到 User ID: 1
在 router 对象下没有 param 属性,都是存放在 query 参数中,例如访问 user/1?username=kuizuo,此时的 query 值为 {username: 'kuizuo', id: '2'}
不过这里有个比较有意思的点,如果你在上方代码中使用 console.log 打印 query 的话,在 vscode 中会打印出空对象{},而在浏览器中会打印一次空对象,一次真实的 query 对象(并且打印两遍)

数据渲染
如果你打开控制台,查看所返回的页面,你会发现响应中只有 User id:,这不就和 react 的 CSR(客户端)渲染没有区别吗,是的,确实是这样。因为上一部分的代码,并且从输出 query 也可以看的出来而不是 SSR(服务端)渲染。首先我要展示一下两者渲染的代码
CSR 客户端渲染
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
const User = () => {
  const router = useRouter()
  const { id } = router.query
  const [data, setData] = useState({
    username: '',
    email: '',
  })
  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
      .then(res => res.json())
      .then(data => {
        setData(data)
      })
      .catch(err => {})
  }, [id])
  return (
    <div>
      <p>username:{data.username} </p>
      <p>email:{data.email} </p>
    </div>
  )
}
export default User
经常写 react 的肯定对上面的代码不陌生,前端向后端发送数据请求,接受到数据后赋值给 data,然后渲染出来。因为请求数据是需要耗时的,所以在页面显示完之后,会停顿一会在显示出数据(主要是我这边没写 loadding),并且由于 id 并不是第一时间获取到的(从上面的 id)。

从这里来看,客户端渲染不仅要获取页面组件,还要请求数据,最终再通过 js 渲染出来
SSR 服务端渲染
next 中服务端渲染需要用到 getServerSideProps 函数,而后端的数据获取都是在该函数内来获取,并通过 prop 传入给前端组件中,来看实际代码
const User = ({ data }: { data: any }) => {
  return (
    <div>
      <p>username:{data.username} </p>
      <p>email:{data.email} </p>
    </div>
  )
}
export default User
export async function getServerSideProps(context: { query: { id: any } }) {
  const { id } = context.query // 这里context.param也能获取到id
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
  const data = await res.json()
  return {
    props: {
      data,
    },
  }
}
如果从页面显示来看,确实没什么区别,但如果打开控制台就能发现诸多不同。
首先就是请求的页面,是直接包含数据,相当于返回你一个页面,而在客户端渲染则是返回一个组件,需要自己去请求数据来展示。

同时查看控制台中的 Fetch/XHR 的是看不到请求的数据,因为这些数据并不是由前端发 送的,而是由后端发送的(故不受跨域请求的限制)。
从这就能看出客户端渲染与服务端渲染的的区别了。
SSG 静态生成
不过还没完,还有一个静态生成,先来看看代码。
const User = ({ data }: { data: any }) => {
  return (
    <div>
      <p>username:{data.username} </p>
      <p>email:{data.email} </p>
    </div>
  )
}
export default User
export async function getStaticProps(context: { params: { id: any } }) {
  const { id } = context.params
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
  const data = await res.json()
  return {
    props: {
      data,
    },
  }
}
export async function getStaticPaths() {
  return {
    paths: new Array(20).fill(0).map((a, i) => ({ params: { id: String(i + 1) } })),
    fallback: 'blocking',
  }
}
主要是 getServerSideProps 替换成 getStaticProps,同时增加了一个 getStaticPaths 用于生成静态页面的,而上面的 getStaticPaths 表示生成 id 1 到 20 的页面,那假设如果我访问 id 为 21 的 user 呢?由于这里设置fallback: 'blocking',所以还是会走服务端渲染的那一部分。但如果设置fallback: fasle,访问 user/21 就会提示 404。
通俗点来说就就是生成一系列静态页面,不需要服务端处理,所以返回的速度更快,其缺点其实也比较明显,数据的任何更改都需要在服务端重新构建,而服务端渲染则是可以动态处理数据,不需要完全重建。
ISR 增量式静态生成
不做过多介绍,详看文档 Data Fetching: Incremental Static Regeneration | Next.js (nextjs.org)
api 接口
上面的数据都是调用 JSONPlaceholder 所提供的虚拟数据,在 next 中要提供数据接口的话,只需要在 pages/api 下编写即可,生成的路由规则和组件一样。例如 pages/api/hello.ts 映射为 api/hello ,浏览器访问http://localhost:3000/api/hello 就可以得到{"name": "John Doe"}
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
  name: string
}
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
  res.status(200).json({ name: 'John Doe' })
}
这里的 req、res 就是同大部分 node 后端框架一样,而这里的写法与 serverless 一致(这里应该就是 serverless)。
上述是 get 请求,那 post 请求呢?无论什么 http 请求方法都将在 handler 处理,通过 req.method 来获取请求方法,要区分的话可以通过如下代码。
export default function handler(req, res) {
  if (req.method === 'POST') {
    // Process a POST request
  } else {
    // Handle any other HTTP method
  }
}
写一个简单的 CRUD
既然知道了上述的一些作用,不妨来个熟悉的 CRUD。这里以文章 post 为例
这里数据端使用的时 sqlite,配置不做展示,只展示主要核心功能
import type { NextApiRequest, NextApiResponse } from 'next'
import db from '../../../lib/db'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    switch (req.method) {
      case 'GET':
        db.all(`select * from post`, (err, rows) => {
          res.status(200).json(rows)
        })
        break
      case 'POST':
        const { title, content } = req.body
        db.get(`insert into post(title, content) values(?, ?)`, [title, content], (err, rows) => {
          res.status(200).json(rows)
        })
        break
    }
  } catch (error) {
    res.status(500).end()
  }
}
import type { NextApiRequest, NextApiResponse } from 'next'
import db from '../../../lib/db'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { id } = req.query
  const { title, content } = req.body
  try {
    switch (req.method) {
      case 'GET':
        db.get(`select * from post where id=$id`, { $id: id }, (err, rows) => {
          res.status(200).json(rows)
        })
        break
      case 'Put':
        db.get(
          `update post set title=?,content=? where id=?`,
          [title, content, id],
          (err, rows) => {
            res.status(200).json(rows)
          },
        )
      case 'DELETE':
        db.get(`delete from post where id=$id`, { $id: id }, (err, rows) => {
          res.status(200).json(rows)
        })
    }
  } catch (error) {
    res.status(500).end()
  }
}
这里为了符合 RESTFUL 风格,所以 post 下编写了两个文件,这时候请求http://localhost:3000/api/post 就能获取到所有文章数据,基本的 CRUD 也就实现了。
这里写 sql 是真滴繁琐,没使用 str 或是 typeorm 主要是不想把这个 demo 搞得太复杂,实际项目还是用上比较好。
当然这里只是作为后端 api 接口的演示,至于前端的展示与编写就和普通前端开发没啥大的区别。基本后端框架能做的,next 能做后端很多事情,更多的使用还是作为接口转发,中间件等,毕竟 Next 主要的强项还是服务端渲染的能力。