Next.js是一个用于构建全栈Web应用程序的React框架。你可以使用React组件来构建用户界面,使用Next.js来实现额外的功能和优化。 在引擎盖下,Next.js还抽象并自动配置React所需的工具,如捆绑,编译等。这使您可以专注于构建应用程序,而不是花费时间进行配置。
主要特点 路由,渲染,数据获取,样式,优化项,TypeScript支持。
安装
Node.js 18.17或者以后
# https://nodejs.org/en
# 使用create-next-app启动一个新的Next.js应用程序
sudo npx create-next-app@latest
What is your project named? next-project
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias (@/*)? No / Yes
What import alias would you like configured? @/*
项目结构
// app 目录就是我们选择的App Router,在app文件夹创建文件夹及相关文件将对应相应的路由
// public 服务的静态资产
// components文件夹,用于放置自定义的组件
// styles文件夹,用于放置样式文件,当前使用的是CSS in JS方式
// lib文件夹,用于放置自定义的方法工具等
构建应用程序
// 文件名(后缀.js .jsx .tsx) 描述
layout 路由及其子路由的共享UI
page 路由的唯一UI并使路由可公开访问
loading 路由加载及其子路由加载的UI
not-found 找不到路由及其子路由的UI
error Error UI for a segment and its children段及其子段的错误UI
global-error 全局错误UI,在app(根)目录下
route 服务器端API端点
template 专门的重新渲染布局UI
default 并行路由的回退UI
定义路由
Next.js使用基于文件系统的路由器,其中文件夹用于定义路由。
//如 /dashboard/settings 映射到 app/dashboard/settings/page.tsx
//page.tsx
export default function Page() {
return <h1>Hello, Next.js!</h1>
}
页面和布局
Next.js 13中的App Router引入了新的文件约定,可以轻松创建页面、共享布局和模板。本篇将指导您如何在Next.js应用程序中使用这些特殊文件。
//app/page.js
export default function Page() {
return <h1>Hello, Home page!</h1>
}
//app/dashboard/page.tsx
export default function Page() {
return <h1>Hello, Dashboard Page!</h1>
}
//app/layout.js 必须的
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{/* Layout UI */}
<main>{children}</main>
</body>
</html>
)
}
//app/dashboard/layout.tsx
export default function DashboardLayout({
children, // will be a page or nested layout
}: {
children: React.ReactNode
}) {
return (
<section>
<Links linkList={['dashboard', 'dashboard/settings']} />
<nav>DashboardLayout</nav>
{children}
</section>
)
}
//根布局(app/layout.js)将包裹(app/dashboard/layout.js),这将包裹app/dashboard/*中的界面。
修改
元数据即html文件中head标签下的内容,可以在layout.tsx或page.tsx中导出metadata对象或generateMetadata函数来定义,如src/app/page.tsx:
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}
链接和导航
在Next.js中,有四种方法可以在路由之间导航:,useRouter,redirect function,window.history.pushState
//app/page.tsx
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
// 有状态的链接
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
</li>
<li>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</li>
</ul>
</nav>
)
}
//layout 引入
<Links></Links>
// useRouter 只能用在客户端
'use client'
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
//redirect function
import { redirect } from 'next/navigation'
async function fetchTeam(id: string) {
const res = await fetch('https://...')
if (!res.ok) return undefined
return res.json()
}
export default async function Profile({ params }: { params: { id: string } }) {
const team = await fetchTeam(params.id)
if (!team) {
redirect('/login')
}
// ...
}
路由分组
在 app 目录中,嵌套文件夹通常映射到 URL 路径。但是,您可以将文件夹标记为路由组,以防止该文件夹包含在路由的 URL 路径中。这允许您将路由段和项目文件组织到逻辑组中,而不会影响 URL 路径结构。
//即使路由内部 (marketing) 和 (shop) 共享相同的 URL 层次结构,您也可以通过在文件夹内添加 layout.js 文件来为每个组创建不同的布局。
// 创建下列测试文件
(marketing)/about/page.js
(marketing)/blog/page.js
(shop)/account/page.js
动态路由
如果您提前不知道确切的路由名称,并且想要根据动态数据创建路由,则可以使用在请求时填充或在构建时预呈现的动态路由。 可以通过将文件夹的名称括在方括号中来创建动态路由: [folderName] 。例如, [id] 或 [slug] 。动态路由作为 params prop传递给 、 layout route 、 page 和 generateMetadata 函数。
//src/app/blogs/[id]/page.tsx
export default function Page({ params }: { params: { id: string } }) {
return <div>My Post: {params.id}</div>
}
// app/blogs/[id]/page.js /blogs/a params={ id: 'a' }
加载UI
特殊文件 loading.js 可帮助您使用 React Suspense 创建有意义的加载 UI。使用此约定,您可以在加载路由段的内容时显示来自服务器的即时加载状态。渲染完成后,新内容将自动交换。
//在src/app/dashboard下新建文件loading.tsx
export default function Loading() {
return <>加载中...</>
}
// 在同一个文件夹中, loading.js 将嵌套在 layout.js .它会自动将 page.js 文件和下面的任何子项包装在 <Suspense> 边界中。
错误处理
error.js文件约定允许您优雅地处理嵌套路由中的意外运行时错误。
//app/dashboard/error.tsx
'use client' // Error components must be Client Components
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
并行路由
并行路由允许您在同一布局中同时或有条件地呈现一个或多个页面。对于应用的高度动态部分(例如社交网站上的仪表板和源),并行路由可用于实现复杂的路由模式。
// 定义了两个显式插槽: app/parallel/@analytics/page.js 和 app/parallel/@team/page.js 。
// 上面的文件夹结构意味着 app/parallel/layout.js 组件中现在接受 @analytics 和 @team 插槽 props,并且可以将它们与 children 并行渲染:
//app/parallel/layout.tsx
export default function RootLayout({
children,
team,
analytics
}: {
children: React.ReactNode
team: React.ReactNode
analytics: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
拦截路由
拦截路由允许您从当前布局中应用程序的另一部分加载路由。当您希望在用户不切换到其他上下文的情况下显示路由的内容时,此路由范式非常有用。
//单击源中的照片时,可以以Modal模态框显示照片,覆盖源。在这种情况下,Next.js 会截获 /photo/123 路由,屏蔽 URL,并将其覆盖 /feed 在 上。
//(.)photos/[id]/page.tsx
import Frame from '../../../../components/frame/Frame'
import Modal from '../../../../components/modal/Modal'
import swagPhotos, { Photo } from '../../../../lib/photos'
export default function PhotoModal({ params: { id: photoId } }: { params: { id: string } }) {
const photos = swagPhotos
const photo: Photo = photos.find(p => p.id === photoId)!
return (
<Modal>
<Frame photo={photo} />
</Modal>
)
}
//具体代码参考源码
API 路由匹配
next.js 首页标榜的 12 个特性之一就是 API routes,简单的说就是可以 next.js 直接写 node 代码作为后端服务来运行。因此我们可以直接使用 next.js 直接维护一个全栈项目,听起来很香的样子。而 API 的文件命名有三种方式:
- pages/api/route.js
- pages/api/route/[param].js
- pages/api/route/[...slug].js
//pages/api/route.js
import { NextApiRequest, NextApiResponse } from "next";
export function GET(
req: NextApiRequest,
) {
return Response.json({ message: 'Hello from Next.js! /api/mapi' })
}
//pages/api/route/[param].js
export async function GET(
request: Request,
{ params }: { params: { postSlug: string } }
) {
const { postSlug } = params;
return Response.json({ message: 'Hello from Next.js! ' + postSlug })
}
//pages/api/route/[...slug].js
export async function GET(
request: Request,
{ params }: { params: { slug: string } }
) {
const { slug } = params;
return Response.json({ message: 'Hello from Next.js! ' + slug })
}
// res 中扩展了以下几个常用的方法:
// res.status(code) 响应的 http 状态码
// res.json(body) json 响应体
// res.send(body) 其它响应体,可以是 string、object、Buffer
// res.redirect([status,] path) 重定向
// res.revalidate(urlPath) 重新进行校验
//req 中则扩展了以下几个常用属性:
// req.cookies 请求包含的 cookies
// req.query 请求的 query 参数
// req.body 请求体
CSR(客户端渲染)
"use client";
import react, { useEffect, useState } from "react";
type Product = {
id: string;
title: string;
};
const CsrPage = () => {
const [products, setProducts] = useState<Product[]>([]);
const fetchData = async () => {
// const res = await fetch("https://dummyjson.com/products");
// const data = await res.json();
setProducts([{id:'1234',title:"aaa"},{id:'1235',title:"bbb"}]);
};
useEffect(() => {
fetchData();
}, []);
return (
<>
<h3>Built with CSR</h3>
<br />
<ul>
{products.map((product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
};
export default CsrPage;
SSR(服务器端渲染)
当接收到来自客户端的请求时,服务器在服务器端执行 JavaScript 以生成(呈现)要显示的 HTML。然后生成的 HTML 返回给客户端,客户端在其浏览器中按原样显示该 HTML。 与 CSR 的主要区别在于浏览器端的工作量。在CSR中,客户端的工作是“运行JavaScript并生成要显示的HTML”,但在SSR中,生成HTML的工作在服务器端,因此浏览器只显示返回的HTML。
- 将fetch()的第二个参数设置为{ cache: “no-store” }
- 没有使用useEffect、useState、onClick等依赖于浏览器的功能
import React from "react";
type Product = {
id: string;
title: string;
};
const SsrPage = async () => {
const res = await fetch("https://dummyjson.com/products", {
cache: "no-store",
});
const data = await res.json();
return (
<>
<h3>Built with SSR</h3>
<br />
<ul>
{data.products.map((product: Product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
};
export default SsrPage;
SSG(静态站点生成)
在应用程序构建时生成页面并作为静态页面储存在服务器中。当有请求时,服务器返回该静态文件。
- 在fetch()的第二个参数中,我们设置了{ cache: “force-cache” }(因为这是默认选项,所以可以省略)。
- 未使用useEffect、useState、onClick等依赖于浏览器的功能。
import React from "react";
type Product = {
id: string;
title: string;
};
const SsgPage = async () => {
const res = await fetch("https://dummyjson.com/products",{
cache: "force-cache",
});
const data = await res.json();
return (
<>
<h3>Built with SSG</h3>
<br />
<ul>
{data.products.map((product: Product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
};
export default SsgPage;
ISR(增量静态再生)
这是一种结合了SSR和SSG的渲染方法。应用程序构建时会生成页面,作为静态页面存储在服务器上,发出请求时返回静态文件。到目前为止,行为与 SSG 类似,但 ISR 允许你在一段时间后再次运行构建。它是为了解决 SSG 的弱点而创建的,SSG 偶尔更新时需要手动重建。
- 在fetch()的第二个参数中,设置了{ next: { revalidate: 10 } }
- 未使用useEffect、useState、onClick等依赖于浏览器的功能。
import React from "react";
type Product = {
id: string;
title: string;
};
const IsrPage = async () => {
const res = await fetch("https://dummyjson.com/products", {
next: {
revalidate: 30,
},
});
const data = await res.json();
return (
<>
<h3>Built with ISR</h3>
<br />
<ul>
{data.products.map((product: Product) => (
<li key={product.id}>
{product.id}: {product.title}
</li>
))}
</ul>
</>
);
};
export default IsrPage;
构建
我们可以通过构建来检查 SSR、SSG 和 ISR 的行为,使用以下命令执行构建:
npm run build
// { cache: “force-cache” }或者没有指定任何内容👉 SSG
// { cache: “no-store” } 指定👉SSR
// { next: { revalidate: 10 } }指定👉 ISR