官网 https://nextjs.org/docs/app/getting-started
Next.js 简介
Next.js 由 Vercel 开发和维护,旨在解决单页应用(SPA)和多页应用(MPA)在性能和 SEO 上的不足。
核心特性
- 服务端渲染(SSR)-- 在服务器端预渲染页面,将 HTML 直接发送到客户端,从而提高了页面加载速度和 SEO 效果。
- 静态生成(SSG)-- 在构建时生成 HTML 的方式,使得每个页面的内容都能在构建时生成并缓存,这样无需在每次请求时生成 HTML,从而显著提升页面性能。
- 通过文件系统的结构快速创建页面路由。同时,API 路由功能使开发者能够直接在 Next.js 项目中创建后端 API
- 增量静态再生成(ISR)-- 将部分页面静态化,并在内容更新时触发重新生成,以确保页面内容的新鲜度和快速访问。
- 内置图像优化功能,可通过
<Image />
组件自动进行图像懒加载、响应式优化和格式转换,有效减少加载时间并提升页面性能。 - 对 CSS 模块、Sass 和 styled-jsx 原生支持,支持 Tailwind CSS 和其他流行的 CSS 框架
适用场景
- 内容驱动的网站:如博客、新闻网站等,Next.js 的 SSG 和 ISR 特性使其在内容频繁更新时,能够保持高效的性能和良好的 SEO 表现。
- 电商平台:对于电商网站,Next.js 的 SSR 和 ISR 能够确保页面快速响应,满足用户体验的需求,同时也便于动态加载产品信息。
- Web应用:复杂的企业级应用或 SaaS 应用可以利用 Next.js 提供的 SSR、动态路由和 API 路由实现高性能的全栈架构。
- 个人网站和组合展示:对于个人博客和作品展示,Next.js 提供了静态生成和图像优化功能,能够确保简洁高效的页面展示。
搭建开发环境,创建项目
- 安装 Node.js
- 创建项目
npx create-next-app@latest
会自动安装依赖
- 用 vscode 打开项目,并启动
浏览器访问 http://localhost:3000/ 效果如下
项目目录
- src\app\page.tsx 项目首页
- src\app\layout.tsx 页面布局
- public 静态资源,例如图像、字体等
页面路由
会根据 src\app 内的文件自动创建,具体对应关系范例如下:
路由 | 文件路径 |
---|---|
/ | src\app\page.tsx |
/blog | src\app\blog\page.tsx |
/blog/动态参数 | src\app\blog\[slug]\page.tsx |
- 所有页面路由都对应一个 page.tsx
- 含动态参数的路由,对应
[slug]
文件夹下的 page.tsx
app/
├── layout.jsx // 布局组件
├── page.jsx // 首页,映射到 "/"
├── about/
│ └── page.jsx // 关于页面,映射到 "/about"
├── blog/
│ ├── layout.jsx // 博客专属布局
│ └── [slug]/
│ └── page.jsx // 动态路由,映射到 "/blog/:slug"
├── api/hello/
│ └── route.jsx // API 路由,映射到 "/api/hello"
└── globals.css // 全局样式
页面中获取路由动态参数的方法如下
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<>
<h1>第 {slug} 篇博客的标题</h1>
<p>第 {slug} 篇博客的内容</p>
</>
);
}
- 页面函数组件的参数为 { params,}
- 函数参数的类型为 { params: Promise<{ slug: string }>;},可见参数 params 是一个 Promise
- 需用 await 同步获取到 params ,并解构出其中的 slug
- 解构出的 slug 值即路由上的动态参数,以
/blog/1
为例,slug 的值为 1
路由跳转
Link
内置的 Link 组件,点击可实现路由跳转
import Link from "next/link";
<Link href="/blog">板块--博客</Link>
页面布局
每个文件夹下的 layout.tsx 文件,即该文件夹对应路由的页面布局
布局 | 布局文件路径 |
---|---|
全局布局 | src\app\layout.tsx |
/blog 的布局 | src\app\blog\layout.tsx |
以 blog 板块的布局为例,src\app\blog\layout.tsx 的内容为
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<header>页头-博客板块</header>
<section>{children}</section>
</>
);
}
- BlogLayout 为任意自定义的布局函数名称
- 函数的参数为 {children:children} , 简写为 {children}
- 函数参数的类型为 {children:React.ReactNode}
- 函数的返回内容需含有 {children} ,其他内容可任意自定义
服务端组件 vs 客户端组件
https://blog.csdn.net/weixin_41192489/article/details/145629124
内置方案
字体 next/font
https://blog.csdn.net/weixin_41192489/article/details/145625560
图片 Image
https://blog.csdn.net/weixin_41192489/article/details/145629598
样式 CSS
https://blog.csdn.net/weixin_41192489/article/details/145625489
后端服务
Next.js 不仅能构建前端页面,还同时提供了后端服务
src\app\api 内的 route.js 会自动生成后端接口
范例:src\app\api\blog\route.js
import { NextResponse } from "next/server";
// 处理 GET 请求
export async function GET(request) {
// 这里可以编写从数据库或其他数据源获取用户数据的逻辑
const data = [
{
id: 1,
title: "第1篇博客的标题",
content: "第1篇博客的内容",
},
{
id: 2,
title: "第2篇博客的标题",
content: "第2篇博客的内容",
},
];
return NextResponse.json(data);
}
启动项目后,浏览器访问 http://localhost:3000/api/blog ,效果如下:
获取数据(访问接口)
https://blog.csdn.net/weixin_41192489/article/details/145632442
实战范例
src\app\page.tsx
import Link from "next/link";
export default function Page() {
return (
<>
<h1>首页</h1>
<div>
<Link href="/blog">板块--博客</Link>
</div>
<div>
<Link href="/note">板块--笔记</Link>
</div>
</>
);
}
src\app\layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<header>页头--全局</header>
{children}
</body>
</html>
);
}
src\app\blog\page.tsx
import Link from "next/link";
interface blog {
id: number;
title: string;
content: string;
}
export default async function Page() {
const data = await fetch("http://localhost:3000/api/blog");
const blogList = await data.json();
return (
<>
<h1>博客列表</h1>
<ul>
{blogList.map((blog: blog) => (
<li key={blog.id}>
<Link href={`/blog/${blog.id}`}>{blog.title}</Link>
</li>
))}
</ul>
</>
);
}
src\app\blog\layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<header>页头-博客板块</header>
<section>{children}</section>
</>
);
}
src\app\blog[slug]\page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<>
<h1>第 {slug} 篇博客的标题</h1>
<p>第 {slug} 篇博客的内容</p>
</>
);
}
src\app\api\blog\route.js
import { NextResponse } from "next/server";
// 处理 GET 请求
export async function GET(request) {
// 这里可以编写从数据库或其他数据源获取用户数据的逻辑
const data = [
{
id: 1,
title: "第1篇博客的标题",
content: "第1篇博客的内容",
},
{
id: 2,
title: "第2篇博客的标题",
content: "第2篇博客的内容",
},
];
return NextResponse.json(data);
}