Next.js Page Router

152 阅读6分钟

Next.js 是一个 React web 应用框架,这是官方对自己的定义,然后它主要做的事情有以下几点:

  • 完善的工程化机制
  • 良好的开发和构建性能
  • 智能文件路由系统
  • 多种渲染模式来保证页面性能体验
  • 可扩展配置
  • 提供其他多方面性能优化方案
  • 提供性能数据,让开发者更好的分析性能。
  • 提供的其他常用功能或者扩展,比如使用 mdx 来编写页面的功能等等。

安装创建和配置

创建项目:

npx create-next-app@latest --typescript

创建项目成功后,运行 npm run dev 启动项目,通过 http://localhost:3000 访问

生成的 npm scripts:

// package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}
  • dev:运行next dev在开发模式下启动Next.js。
  • build:运行next build以构建用于生产使用的应用程序。
  • start:运行next start以启动Next.js生产服务器。
  • lint:运行next lint来设置Next.js的内置ESLint配置。

设置绝对导入和模块路径别名:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src/",
    "paths": {
      "@/styles/*": ["styles/*"],
      "@/components/*": ["components/*"]
    }
  }
}

内置了以下工程化:

  • babel 内置,支持JS代码向后兼容
  • postcss 内置,支持CSS代码向后兼容
  • browserslist 支持配置兼容的浏览器信息,配合 babel 和 postcss 工作。
  • TypeScript 可选择使用,保证代码的质量,以及可阅读性和可维护性。
  • eslint 可选择使用,检测代码格式,可自定义规则。vscode 编写代码,或者build打包时都会有提示。
  • prettier 可通过扩展使用,格式化代码,可自定义规则。
  • css modules 内置
  • css-in-js 可扩展使用
  • tailwind css 可扩展使用

内置的打包优化功能:

  • tree shaking
  • 代码压缩
  • 页面自动静态化
  • 按需打包第三方 es 包(通过设置 transpilePackages 属性,让部分包可以被 next-babel 打包)
  • 异步动态加载组件,和 React.lazy 功能类似

项目结构

项目结构

Next.js 多渲染模式

  如果页面中存在getServerSidePropsgetInitialProps,Next.js将按照 SSR 方式渲染

  否则,Next.js将使用通过 SSG 方式渲染页面。

  页面组件中使用了 React.useEffect 客户端 API 时使用 CSR 渲染方式

  1. 服务器端渲染 SSR

每次进入页面时,从服务器端生成 html 返回给客户端渲染页面。

页面中需要export一个名为getServerSidePropsasync函数返回组件的 props。服务器将在每次请求时调用此函数。

export default function Page({ data }) {
  // Render data...
}
 
// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()
 
  // Pass data to the page via props
  return { props: { data } }
}
  1. 静态站点生成 SSG

当运行next build时,会生成页面的 HTML。然后,此HTML可以通过CDN缓存在每个请求中重复使用。

页面组件需要获取外部数据进行预渲染。有两种情况:

  • 页面内容取决于外部数据:使用getStaticProps
  • 页面路径取决于外部数据:使用getStaticPaths。一般结合动态路由
export default function Blog({ posts }) {
  // Render posts...
}
 
// 返回的数据用于页面组件的 props
export async function getStaticProps() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  }
}

// pages/posts/[id].js,返回动态路由 id 对应数据
export async function getStaticPaths() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))
 
  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false }
}

不需要外部数据,使用静态生成渲染页面

function About() {
  return <div>About</div>
}
 
export default About
  1. 客户端渲染 CSR

浏览器下载了最低限度的HTML页面和页面所需的JavaScript。然后使用JavaScript来更新DOM并渲染页面

在Next.js中,有两种方法可以实现客户端渲染:

  • 在页面中使用React的useEffect()钩子
  • 使用像SWR这样的数据获取库或TanStack查询获取客户端上的数据(推荐)
import useSWR from 'swr'
 
 // 使用 SWR 
export function Page() {
  const { data, error, isLoading } = useSWR(
    'https://api.example.com/data',
    fetcher
  )
 
  if (error) return <p>Failed to load.</p>
  if (isLoading) return <p>Loading...</p>
 
  return <p>Your Data: {data}</p>
}
import React, { useState, useEffect } from 'react'
 
 // 使用了 useEffect
export function Page() {
  const [data, setData] = useState(null)
 
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data')
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      const result = await response.json()
      setData(result)
    }
 
    fetchData().catch((e) => {
      // handle the error as needed
      console.error('An error occurred while fetching the data: ', e)
    })
  }, [])
 
  return <p>{data ? `Your data: ${data}` : 'Loading...'}</p>
}

路由和布局

Next.js 是围绕着 页面(pages) 的概念构造。一个页面(page)就是一个从 pages 目录下的 .js、.jsx、.ts 或 .tsx 文件导出的 React 组件。

页面基于基于文件系统,页面(page) 根据其文件名与路由关联。例如,pages/about.js 被映射到 /about。

页面与路由映射:

  • 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/posts/[id].js -> /posts/xxx 创建动态路由

项目的统一布局:

// components/layout.tsx
import Navbar from './navbar'
import Footer from './footer'
 
export default function Layout({ children }: { children: ReactElement }) {
  return (
    <>
      <Navbar />
      <main>{children}</main>
      <Footer />
    </>
  )
}
// pages/_app.tsx
import Layout from '../components/layout'
import type { AppProps } from 'next/app'
 
type AppPropsWithLayout = AppProps & {  Component: NextPageWithLayout}

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

页面有独立自定义的布局时:

// pages/index.tsx
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
 
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}
 
type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}
 
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  // Use the layout defined at the page level, if available
  const getLayout = Component.getLayout ?? ((page) => page)
 
  return getLayout(<Component {...pageProps} />)
}
// pages/_app.tsx

import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
 
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}
 
type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}
 
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  // Use the layout defined at the page level, if available
  const getLayout = Component.getLayout ?? ((page) => page)
 
  return getLayout(<Component {...pageProps} />)
}

Page Functions

  • getInitialProps async 函数,在页面切换时调用,服务器、客户端上都可能执行
  • getServerSideProps 函数,获取经常更改的数据,并更新页面以显示最新数据,服务端渲染时调用,
  • getStaticPaths 函数,构建时返回预渲染的静态页面对应的路径数据
  • getStaticProps 函数,在构建时返回页面组件的 props

Page api:

Page hook:

  • useAmp hook,支持网络组件框架,加速移动页面功能
  • useReportWebVitals hook,数据收集分析功能
  • useRouter hook,组件中操作 router 对象
  • userAgent hook,扩展 Fetch API 的 Request,与请求中的用户代理对象进行交互。

Next Component