官网 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
页面中获取路由动态参数的方法如下
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} ,其他内容可任意自定义
后端服务
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 ,效果如下:
内置组件
图片 Image
Next.js 内置的 next/image 图像优化组件,通过以下功能提升图像性能:
- 图片格式统一转换为 WebP,并根据设备尺寸自动调整分辨率
- 避免了图片加载过程中的布局偏移(Layout Shift)
- 默认情况下,图像在进入视窗时才加载,可选择使用模糊占位符。
- 通过设置尺寸属性,图像可以根据屏幕大小自适应。
图片存于目录 public\images 中
import Image from "next/image";
本地图片
<Imagesrc="/images/风景.png"alt="图片-风景"width={600}height={400}// 开启响应式 -- 根据宽高比调整图像尺寸,确保在不同设备上始终显示最佳比例。layout="responsive" // 禁用懒加载 -- 可提高页面加载图片的优先级priority// 图片加载时的占位图blurDataURL="/images/img_loading.png"/>
远程图片
需先在 next.config.ts 中配置图片域名
import type { NextConfig } from "next";const nextConfig: NextConfig = {// 配置图片域名images: {remotePatterns: [{hostname: "se2.360simg.com",},],},
};export default nextConfig;
再在页面中使用
<Imagesrc="https://se2.360simg.com/t01d8935450f8ea8ffb.png"alt="图片-远程"width={600}height={400}priority // 提高页面加载时的优先级blurDataURL="/images/img_loading.png"/>
响应式图片 sizes
<Imagefillsrc="/example.png"sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"/>
图片加载占位符 blurDataURL
- 需同时设置
placeholder="blur"
- blurDataURL 的值为 base64的图片(建议用极小的)
import Image from "next/image";const fs = require("fs");const imagePath = "./public/images/img_loading.png";
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = imageBuffer.toString("base64");
const blurDataURL = `data:image/jpeg;base64,${base64Image}`;export default function Page() {return (<><Imagesrc="https://se2.360simg.com/t01d8935450f8ea8ffb.png"width={600}height={400}placeholder="blur"// 图片加载时的占位图blurDataURL={blurDataURL}/></>);
}
字体
“next/font” 模块对字体进行了优化:
- 避免网络请求,提高了隐私性和性能
- 自托管的网页字体不会有布局偏移
传统的 Web 字体加载方式:网络请求字体文件,在字体加载完成前,页面显示为空白或默认字体,加载完成后,页面的字体突然改变,可能引发布局偏移
Google 字体 【默认】
在创建项目时,便自动采用了 Google 字体 ,见 src\app\layout.tsx
import { Geist, Geist_Mono } from "next/font/google";
const geistSans = Geist({variable: "--font-geist-sans",subsets: ["latin"],
});const geistMono = Geist_Mono({variable: "--font-geist-mono",subsets: ["latin"],
});
<bodyclassName={`${geistSans.variable} ${geistMono.variable} antialiased`}>
本地字体
-
将本地字体文件放入 src\app 中,如
src\app\昆明海鸥体.ttf
百度云盘下载链接: https://pan.baidu.com/s/1rLFdlFoD5oTVNMUM6e4pOA?pwd=4526
-
将 src\app\layout.tsx 修改为
import type { Metadata } from "next";import "./globals.css";// 引入本地字体处理函数 localFont import localFont from "next/font/local";// localFont函数将本地字体文件转换为字体类名 const myFont = localFont({src: "./昆明海鸥体.ttf", });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 上添加字体类名<body className={myFont.className}>{children}</body></html>); }
效果如下:
实战范例
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"><bodyclassName={`${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);
}