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 多渲染模式
如果页面中存在getServerSideProps或getInitialProps,Next.js将按照 SSR 方式渲染
否则,Next.js将使用通过 SSG 方式渲染页面。
页面组件中使用了 React.useEffect 客户端 API 时使用 CSR 渲染方式
- 服务器端渲染 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 } }
}
- 静态站点生成 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
- 客户端渲染 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:
- NextRequest,扩展了 Fetch API 的 Request 对象
- NextResponse,扩展了 Fetch API 的 Response 对象
Page hook:
- useAmp hook,支持网络组件框架,加速移动页面功能
- useReportWebVitals hook,数据收集分析功能
- useRouter hook,组件中操作 router 对象
- userAgent hook,扩展 Fetch API 的 Request,与请求中的用户代理对象进行交互。
Next Component
- Font 自动优化您的字体(包括自定义字体),并删除外部网络请求
- 扩展了HTML
<form>元素,在提交时不用重新加载整页,渐进式增强 - 将组件内子元素附加到 HTML
扩展了HTML
<img>元素,提供了图片的自动优化功能- 扩展了HTML
<a>元素,以提供在访问路由之前在后台预加载路由的方法。 - 为多个路由加载第三方脚本,导入
next/script,并将脚本直接包含在布局组件中使用