Next.js 备忘清单

NPM version Downloads Repo Dependents Github repo

这是一份快速参考备忘单,包含 Next.js 的 API 参考列表和一些示例

入门

创建项目

npx create-next-app@latest
# or
yarn create next-app
# or
pnpm create next-app

或者创建 TypeScript 项目

npx create-next-app@latest --typescript
# or
yarn create next-app --typescript
# or
pnpm create next-app --typescript

运行 npm run devyarn devpnpm dev 以在 http://localhost:3000 上启动开发服务器

添加首页

使用以下内容填充 pages/index.js

function HomePage() {
  return <div>Welcome to Next.js!</div>
}

export default HomePage

Next.js 是围绕页面的概念构建的。 页面是从 pages 目录中的 .js.jsx.ts.tsx 文件导出的 React 组件

getServerSideProps

function Page({ data }) {
  // 渲染数据...
}

// 每个请求都会调用它
export async function getServerSideProps() {
  // 从外部 API 获取数据
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // 通过 props 向页面传递数据
  return { props: { data } }
}

export default Page

如果您从页面导出一个名为 getServerSideProps(服务器端渲染)的函数,Next.js 将使用 getServerSideProps 返回的数据在每个请求上预渲染该页面

  • 当您直接请求此页面时,getServerSideProps 在请求时运行,此页面将使用返回的 props 进行预渲染
  • 当您通过 next/linknext/router 在客户端页面转换上请求此页面时,Next.js 会向服务器发送 API 请求,服务器运行 getServerSideProps

getStaticPaths

// pages/posts/[id].js
export async function getStaticPaths() {
  // 当这是真的时(在预览环境中)不要预呈现任何静态页面(更快的构建,但更慢的初始页面加载)
  if (process.env.SKIP_BUILD_STATIC_GENERATION) {
    return {
      paths: [],
      fallback: 'blocking',
    }
  }

  // 调用外部 API 端点以获取帖子
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // 根据帖子获取我们要预渲染的路径 在生产环境中,预渲染所有页面
  // (构建速度较慢,但初始页面加载速度较快)
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // { fallback: false } 表示其他路由应该 404
  return { paths, fallback: false }
}

如果页面具有动态路由并使用 getStaticProps,则需要定义要静态生成的路径列表

  • 数据来自无头 CMS
  • 数据来自数据库
  • 数据来自文件系统
  • 数据可以公开缓存(非用户特定)
  • 页面必须预渲染(用于 SEO)并且速度非常快 —— getStaticProps 生成 HTML 和 JSON 文件,这两种文件都可以由 CDN 缓存以提高性能

getStaticProps

// 帖子将在构建时由 getStaticProps() 填充
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

// 这个函数在服务器端的构建时被调用。
// 它不会在客户端调用,因此您甚至可以直接进行数据库查询。
export async function getStaticProps() {
  // 调用外部 API 端点以获取帖子。 您可以使用任何数据获取库
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // 通过返回 { props: { posts } },Blog 组件将在构建时接收 `posts` 作为 prop
  return {
    props: {
      posts,
    },
  }
}

export default Blog

在服务器端的构建时被调用

增量静态再生

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

// 这个函数在服务器端的构建时被调用
// 如果启用了重新验证并且有新请求进入,它可能会在无服务器功能上再次调用
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js 将尝试重新生成页面:
    // - 当请求进来时
    // - 最多每 10 秒一次
    revalidate: 10, // 片刻之间
  }
}

// 这个函数在服务器端的构建时被调用
// 如果尚未生成路径,则可能会在无服务器函数上再次调用它
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // 根据帖子获取我们要预渲染的路径
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // 我们将在构建时仅预渲染这些路径
  // { fallback: blocking } 如果路径不存在,服务器将按需呈现页面
  return { paths, fallback: 'blocking' }
}

export default Blog
  • 在初始请求之后和 10 秒之前对页面的任何请求也会被缓存和即时
  • 在 10 秒窗口之后,下一个请求仍将显示缓存的(陈旧的)页面
  • Next.js 在后台触发页面的重新生成
  • 一旦页面生成成功,Next.js 将使缓存失效并显示更新后的页面。如果后台重新生成失败,旧页面仍将保持不变

使用 useeffect 客户端数据获取

import { useState, useEffect } from 'react'

function Profile() {
  const [data, setData] = useState(null)
  const [isLoading, setLoading] = useState(false)

  useEffect(() => {
    setLoading(true)
    fetch('/api/profile-data')
      .then((res) => res.json())
      .then((data) => {
        setData(data)
        setLoading(false)
      })
  }, [])

  if (isLoading) return <p>Loading...</p>
  if (!data) return <p>No profile data</p>

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  )
}

使用 SWR 获取客户端数据

import useSWR from 'swr'

const fetcher = (...args) => fetch(...args).then((res) => res.json())

function Profile() {
  const { data, error } = useSWR('/api/profile-data', fetcher)

  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  )
}

静态文件服务

Next.js 可以在根目录中名为 public 的文件夹下提供静态文件,如图像。 然后,您的代码可以从基本 URL (/) 开始引用 public 中的文件

import Image from 'next/image'

function Avatar() {
  return (
    <Image
      src="/me.png"
      alt="me"
      width="64"
      height="64"
    />
  )
}

export default Avatar

支持的浏览器和功能

Next.js 支持零配置的现代浏览器

  • Chrome 64+
  • Edge 79+
  • Firefox 67+
  • Opera 51+
  • Safari 12+

Next.js 支持在 package.json 文件中配置 Browserslist

{
  "browserslist": [
    "chrome 64",
    "edge 79",
    "firefox 67",
    "opera 51",
    "safari 12"
  ]
}

内置 CSS 支持

添加全局样式表

如果不存在,请创建一个 pages/_app.js 文件。 然后,导入 styles.css 文件

import '../styles.css';

// 在新的“pages/_app.js”文件中需要此默认导出
export default function MyApp({
  Component, pageProps
}) {
  return <Component {...pageProps} />
}

例如,考虑以下名为 styles.css 的样式表

body {
  font-family:
    'SF Pro Text', 'SF Pro Icons',
    'Helvetica Neue', 'Helvetica',
    'Arial', sans-serif;
  margin: 0 auto;
}

从 node_modules 导入样式

对于全局样式表,如 bootstrapnprogress,您应该在 pages/_app.js 中导入文件

// pages/_app.js
import 'bootstrap/dist/css/bootstrap.css'

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

Next.js 9.5.4 开始,您的应用程序中的任何地方都允许从 node_modules 导入 CSS 文件

添加组件级 CSS (CSS Modules)

您无需担心 .error {} 与任何其他 .css.module.css 文件!他将被生成 hash 名称

.error {
  color: white;
  background-color: red;
}

然后,创建 components/Button.js,导入并使用上面的 CSS 文件:

import styles from './Button.module.css'

export function Button() {
  return (
    <button
      type="button"
      // 请注意“error”类
      // 是如何作为导入的“styles”对象的属性访问的
      className={styles.error}
    >
      Destroy
    </button>
  )
}

Sass 支持

Next.js 允许您使用 .scss.sass 扩展名导入 Sass,可以通过 CSS 模块和 .module.scss.module.sass 扩展名使用组件级 Sass

$ npm install --save-dev sass

在使用 Next.js 的内置 Sass 支持之前,请务必安装 sass

自定义 Sass 选项

通过在 next.config.js 中使用 sassOptions 来实现配置 Sass 编译器。例如添加 includePaths

const path = require('path')

module.exports = {
  sassOptions: {
    includePaths: 
        [path.join(__dirname, 'styles')],
  },
}

Sass 变量

/* variables.module.scss */
$primary-color: #64ff00;

:export {
  primaryColor: $primary-color;
}

pages/_app.js 中导入 variables.module.scss

import variables from '../styles/variables.module.scss'

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout color={variables.primaryColor}>
      <Component {...pageProps} />
    </Layout>
  )
}

CSS-in-JS

最简单的一种是内联样式:

function HiThere() {
  return (
    <p style={{ color: 'red' }}>hi 这里</p>
  )
}

export default HiThere

使用 styled-jsx 的组件如下所示:

function HelloWorld() {
  return (
    <div>
      Hello world
      <p>scoped!</p>
      <style jsx>{`
        p { color: blue; }
        div { background: red; }
        @media (max-width: 600px) {
          div { background: blue; }
        }
      `}</style>
      <style global jsx>{`
        body { background: black; }
      `}</style>
    </div>
  )
}

export default HelloWorld

当然,你也可以使用 styled-components

Layouts

基础示例

// components/layout.js
import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
  return (
    <>
      <Navbar />
      <main>{children}</main>
      <Footer />
    </>
  )
}

带有自定义应用程序的单一共享布局

// pages/_app.js
import Layout from '../components/layout'

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

使用 TypeScript

// pages/index.tsx
import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { NextPageWithLayout } from './_app'

const Page: NextPageWithLayout = () => {
  return <p>hello world</p>
}

Page.getLayout = function getLayout(page: ReactElement) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}

export default Page
// 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) {
  // 使用在页面级别定义的布局(如果可用)
  const getLayout = Component.getLayout ?? ((page) => page)

  return getLayout(<Component {...pageProps} />)
}

每页布局

// pages/index.js
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return (
    /** Your content */
  )
}

Page.getLayout = function getLayout(page) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}
// pages/_app.js
export default function MyApp({ Component, pageProps }) {
  // 使用在页面级别定义的布局(如果可用)
  const getLayout = Component.getLayout || ((page) => page)
  return getLayout(<Component {...pageProps} />)
}

数据请求

// components/layout.js
import useSWR from 'swr'
import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
  const { data, error } = useSWR('/api/navigation', fetcher)
  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>
  return (
    <>
      <Navbar links={data.links} />
      <main>{children}</main>
      <Footer />
    </>
  )
}

图片优化

本地图片

import Image from 'next/image'
import profilePic from '../public/me.png'

function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src={profilePic}
        alt="Picture of the author"
        // width={500} 自动提供
        // height={500} 自动提供
        // blurDataURL="data:..." 自动提供
        // placeholder="blur" // 加载时可选的模糊处理
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

远程图片

import Image from 'next/image'