Next.js从入门到精通的万字长文教程(附详细代码)
引言:为什么是 Next.js?
在 React 生态系统中,我们常常面临一些挑战:如何配置服务器端渲染(SSR)以提升首屏加载速度和 SEO?如何实现静态站点生成(SSG)以获得极致的性能?如何优雅地处理路由、API 接口、代码分割和打包优化?
Next.js 的出现,完美地回答了这些问题。它是由 Vercel 公司创建的基于 React 的全功能生产级框架。它提供了一系列开箱即用的特性,让你能专注于业务逻辑而非构建配置。从简单的个人博客到复杂的电子商务平台,Next.js 都能胜任。
Next.js 的核心优势:
零配置: 内置 webpack、Babel、代码分割、热更新等,开箱即用。
服务端渲染: 轻松实现 SSR 和 SSG,提升性能和 SEO。
文件系统路由: 在
pages目录下创建文件即可自动生成路由。API 路由: 在同一个项目中编写前端和后端 API。
出色的开发者体验: 快速刷新、强大的错误提示等。
强大的社区和生态: 拥有庞大的社区支持和丰富的插件。
本教程将分为四个部分:
入门篇: 创建第一个 Next.js 应用,理解核心概念。
核心概念篇: 深入探讨路由、数据获取、样式和 API 路由。
进阶实战篇: 构建一个完整的全栈博客应用。
精通与优化篇: 探索高级特性、性能优化和部署。
第一部分:入门篇 - 创建你的第一个 Next.js 应用
1.1 环境准备与项目创建
首先,确保你的系统安装了 Node.js (版本 14.6.0 或更高)。
Next.js 提供了官方的创建工具 create-next-app,它能快速搭建一个预配置好的 Next.js 项目。
打开你的终端,执行以下命令:
bash
npx create-next-app@latest my-nextjs-app
你会被提示选择一些配置项:
Would you like to use TypeScript?
No / Yes(推荐选择 Yes)Would you like to use ESLint?
Yes(推荐)Would you like to use Tailwind CSS?
No / Yes(根据喜好,这里我们先选 No)Would you like to use src/ directory?
No / Yes(推荐 Yes,保持项目结构清晰)Would you like to use App Router?
Yes(推荐,这是 Next.js 13+ 的现代路由)
我们选择使用 App Router,它是 Next.js 13 引入的基于 React Server Components 的新路由系统,功能更强大,是未来的方向。
等待依赖安装完成后,进入项目目录并启动开发服务器:
bash
cd my-nextjs-app npm run dev
在浏览器中打开 http://localhost:3000,你将看到 Next.js 的欢迎页面。
1.2 项目结构初探
使用 create-next-app 并选择上述配置后,你的项目结构大致如下:
text
my-nextjs-app/ ├── app/ # App Router 的主目录 │ ├── favicon.ico │ ├── globals.css # 全局样式 │ ├── layout.tsx # 根布局组件 │ ├── page.tsx # 首页组件 (对应路由 `/`) │ └── ... ├── public/ # 静态资源目录 (图片、字体等) │ └── ... ├── next.config.js # Next.js 配置文件 ├── package.json ├── eslint.config.mjs # ESLint 配置 └── tsconfig.json # TypeScript 配置
关键文件解释:
app/layout.tsx: 这是根布局。所有页面都会共享这个布局中的内容(如<html>和<body>标签)。它是 Server Component。app/page.tsx: 这是你的首页。当你访问/时,渲染的就是这个组件。public/: 这个目录下的文件可以被直接访问。例如,public/logo.png可以通过/logo.png在浏览器中访问。
1.3 理解 React Server Components vs Client Components
这是 App Router 的核心概念。
Server Components (默认):
在服务器上运行。 你的组件代码不会被打包发送到客户端。
可以直接访问后端资源。 如数据库、API 密钥、文件系统,无需通过 API 路由。
有助于减少客户端包大小。 因为代码在服务端执行。
不能使用状态和生命周期。 不能使用
useState,useEffect等 Hook。不能使用浏览器 API。 如
window,document。
Client Components:
在客户端(浏览器)运行。 和传统的 React 组件一样。
可以使用状态和生命周期。 可以使用所有 React Hook。
可以处理交互性。 如点击事件、表单提交。
需要使用 "use client" 指令。 在文件顶部声明。
示例: 让我们修改 app/page.tsx,看看它们如何协作。
tsx
// app/page.tsx
// 这是一个 Server Component (默认)
// 一个在服务器端获取数据的异步函数 (模拟)
async function fetchServerSideData() {
// 这里可以直接访问数据库或内部 API
await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟延迟
return { message: "Hello from the Server!" };
}
// 一个普通的 Server Component
function ServerGreeting() {
return <p>I was rendered on the server.</p>;
}
export default async function HomePage() {
// 在 Server Component 中可以直接使用 async/await 获取数据
const data = await fetchServerSideData();
return (
<div>
<h1>My First Next.js App</h1>
<p>{data.message}</p>
<ServerGreeting />
{/* 我们引入一个 Client Component */}
<ClientCounter />
</div>
);
}
// --- 新建一个 Client Component ---
// 在 app/components/ClientCounter.tsx
"use client"; // <-- 必须的指令,标记这是一个 Client Component
import { useState } from 'react';
export function ClientCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times (Client Component)</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}在这个例子中:
HomePage,fetchServerSideData,ServerGreeting都在服务器端执行。ClientCounter因为有了"use client",它是一个 Client Component,其代码会被打包发送到浏览器,处理点击事件和状态更新。
这种混合模式让你能够为页面的每个部分选择最合适的渲染环境,从而构建出高性能的应用。
第二部分:核心概念篇 - 深入 Next.js 的基石
2.1 基于文件系统的路由 (App Router)
在 App Router 中,路由由 app 目录下的文件夹结构定义。每个文件夹代表一个路由段(segment)。要创建一个页面,你需要使用 page.tsx 或 page.jsx 文件。
基础路由:
| 文件路径 | 对应路由 |
|---|---|
app/page.tsx | / |
app/about/page.tsx | /about |
app/blog/page.tsx | /blog |
动态路由:
如果你需要捕获动态的路由段(如博客文章 ID),可以使用方括号 [folderName] 来创建动态路由。
| 文件路径 | 对应路由 | 示例 URL |
|---|---|---|
app/blog/[id]/page.tsx | /blog/:id | /blog/123, /blog/my-post |
在 page.tsx 中,你可以通过 params 属性访问到这个动态参数。
tsx
// app/blog/[id]/page.tsx
interface BlogPostProps {
params: {
id: string;
};
}
export default function BlogPost({ params }: BlogPostProps) {
return (
<div>
<h1>Blog Post: {params.id}</h1>
<p>This is the content of blog post with ID: {params.id}.</p>
</div>
);
}布局 (Layouts):
布局是共享的 UI,它在多个页面之间保持状态,并且不会在路由切换时重新渲染。它由 layout.tsx 文件定义。
根布局 (app/layout.tsx): 必须的,它包裹所有页面。
路由组布局: 可以在任何子目录下创建
layout.tsx,它只包裹该目录下的页面。
tsx
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{/* 一个所有页面共享的导航栏 */}
<nav className="bg-blue-600 text-white p-4">
<div className="container mx-auto">
<a href="/" rel="external nofollow" className="text-xl font-bold">My Site</a>
<a href="/about" rel="external nofollow" className="ml-4">About</a>
<a href="/blog" rel="external nofollow" className="ml-4">Blog</a>
</div>
</nav>
{/* 子页面将在这里被渲染 */}
<main className="container mx-auto p-4">{children}</main>
{/* 一个所有页面共享的页脚 */}
<footer className="bg-gray-200 p-4 text-center">
<p>© 2023 My Site</p>
</footer>
</body>
</html>
);
}2.2 数据获取 (Data Fetching)
Next.js 在 Server Components 中扩展了 fetch API,提供了强大的数据获取能力,包括自动缓存和重复数据删除。
三种渲染模式:
静态站点生成: 页面在构建时生成。适用于内容不经常变化的页面(如博客、文档、营销页面)。性能最好。
服务端渲染: 页面在每次请求时生成。适用于高度动态、个性化的页面(如仪表盘、用户主页)。
客户端渲染: 页面在浏览器中生成。适用于具有大量交互、不关心 SEO 的页面部分。
在 App Router 中,你通过 fetch 的选项来控制渲染模式。
tsx
// app/blog/page.tsx
// 这是一个 Server Component
interface Post {
id: number;
title: string;
body: string;
}
// SSG (Static Generation) - 默认行为
// 在构建时获取数据并生成静态页面
async function getStaticPosts(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
// cache: 'force-cache' 是默认值,等同于 SSG
});
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
return res.json();
}
// SSR (Server-Side Rendering)
// 在每次请求时获取数据
async function getServerSidePosts(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
cache: 'no-store', // <-- 这表示不缓存,每次请求都重新获取
});
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
return res.json();
}
// ISR (Incremental Static Regeneration)
// 静态生成,但可以定期在后台重新验证和更新
async function getIncrementalPosts(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
next: { revalidate: 60 }, // <-- 每60秒重新验证一次
});
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
return res.json();
}
export default async function BlogListPage() {
// 选择一种数据获取方式
const posts = await getStaticPosts();
return (
<div>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/blog/${post.id}`}>{post.title}</a>
</li>
))}
</ul>
</div>
);
}2.3 样式化 (Styling)
Next.js 不限定你使用何种 CSS 方案。你可以使用:
全局 CSS: 在
app/globals.css中导入,会应用到所有页面。CSS Modules: 创建
[name].module.css文件,样式局部作用于组件。Tailwind CSS: 功能优先的 CSS 框架,与 Next.js 集成度极高。
Styled-components / Emotion: CSS-in-JS 库(通常需要在 Client Components 中使用)。
Sass: 支持
.scss和.sass文件。
以 CSS Modules 为例:
css
/* app/components/Button.module.css */
.primary {
background-color: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.primary:hover {
background-color: darkblue;
}tsx
// app/components/Button.tsx
"use client"; // 如果要用 onClick,就需要是 Client Component
import styles from './Button.module.css';
interface ButtonProps {
children: React.ReactNode;
onClick: () => void;
}
export function Button({ children, onClick }: ButtonProps) {
return (
<button className={styles.primary} onClick={onClick}>
{children}
</button>
);
}2.4 API 路由 (API Routes)
Next.js 允许你在 app 目录下创建 API 端点,这意味着你可以在同一个项目中编写全栈应用。
API 路由位于 app/api/ 目录下,使用 route.ts 文件定义。
创建一个简单的 GET API:
tsx
// app/api/hello/route.ts
export async function GET() {
// 这里可以连接数据库,处理业务逻辑
return Response.json({ message: 'Hello from the API!' });
}现在,访问 http://localhost:3000/api/hello 将会返回 JSON 数据。
处理不同的 HTTP 方法:
tsx
// app/api/users/route.ts
import { NextRequest } from 'next/server';
// GET /api/users
export async function GET() {
const users = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
return Response.json(users);
}
// POST /api/users
export async function POST(request: NextRequest) {
const body = await request.json();
// 在这里,你可以将 body 中的数据保存到数据库
console.log('Creating user with data:', body);
// 模拟创建用户
const newUser = { id: 3, ...body };
return Response.json(newUser, { status: 201 });
}你可以在 Client Component 中使用 fetch 来调用这些 API。
tsx
"use client";
// ... 在某个 Client Component 中
const handleCreateUser = async () => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' }),
});
const newUser = await response.json();
console.log('User created:', newUser);
};第三部分:进阶实战篇 - 构建一个全栈博客应用
现在,让我们运用所学的知识,构建一个简单的全栈博客应用。它将包含:
博客文章列表(SSG)
单篇博客文章页面(动态路由 + SSG)
一个用于创建新博客文章的 API 端点
3.1 项目结构与数据层
我们使用一个本地的 JSON 文件来模拟数据库。在实际项目中,你会使用 Prisma、Drizzle 等 ORM 连接 PostgreSQL、MySQL 等数据库。
创建 lib/posts.ts 来处理数据:
tsx
// lib/posts.ts
export interface Post {
id: string;
title: string;
content: string;
date: string;
}
// 模拟数据
let posts: Post[] = [
{
id: '1',
title: 'First Post',
content: 'This is the content of the first post.',
date: '2023-10-25',
},
{
id: '2',
title: 'Second Post',
content: 'This is the content of the second post.',
date: '2023-10-26',
},
];
// 获取所有文章
export function getPosts(): Post[] {
return posts;
}
// 根据 ID 获取文章
export function getPostById(id: string): Post | undefined {
return posts.find(post => post.id === id);
}
// 创建新文章
export function createPost(post: Omit<Post, 'id'>): Post {
const newPost: Post = {
...post,
id: Date.now().toString(), // 简单的 ID 生成
};
posts.push(newPost);
return newPost;
}3.2 博客列表页面 (SSG)
tsx
// app/blog/page.tsx
import Link from 'next/link';
import { getPosts } from '@/lib/posts';
// 这个页面将在构建时静态生成
export default function BlogListPage() {
const posts = getPosts();
return (
<div>
<h1 className="text-3xl font-bold mb-8">My Blog</h1>
<ul className="space-y-4">
{posts.map((post) => (
<li key={post.id} className="border-b pb-4">
<Link href={`/blog/${post.id}`} className="text-xl font-semibold text-blue-600 hover:underline">
{post.title}
</Link>
<p className="text-gray-500 text-sm">{post.date}</p>
<p className="mt-2">{post.content.slice(0, 100)}...</p>
</li>
))}
</ul>
</div>
);
}3.3 博客文章详情页面 (动态路由 + SSG)
tsx
// app/blog/[id]/page.tsx
import { notFound } from 'next/navigation';
import { getPostById, getPosts } from '@/lib/posts';
interface BlogPostProps {
params: {
id: string;
};
}
// 生成静态参数,告诉 Next.js 哪些 [id] 需要在构建时预渲染
export async function generateStaticParams() {
const posts = getPosts();
// 为每篇文章生成 { id: post.id }
return posts.map((post) => ({
id: post.id,
}));
}
export default function BlogPostPage({ params }: BlogPostProps) {
const post = getPostById(params.id);
// 如果没找到文章,返回 404 页面
if (!post) {
notFound();
}
return (
<article>
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<p className="text-gray-500 mb-8">{post.date}</p>
<div className="prose max-w-none">
{/* 假设你使用 Tailwind @tailwindcss/typography 插件来美化内容 */}
<p>{post.content}</p>
</div>
</article>
);
}3.4 创建文章的 API 端点
tsx
// app/api/posts/route.ts
import { NextRequest } from 'next/server';
import { createPost } from '@/lib/posts';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { title, content } = body;
// 简单的验证
if (!title || !content) {
return Response.json({ error: 'Title and content are required' }, { status: 400 });
}
const newPost = createPost({
title,
content,
date: new Date().toISOString().split('T')[0], // YYYY-MM-DD
});
return Response.json(newPost, { status: 201 });
} catch (error) {
return Response.json({ error: 'Internal Server Error' }, { status: 500 });
}
}3.5 创建一个表单来发布新文章 (Client Component)
tsx
// app/components/CreatePostForm.tsx
"use client";
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export function CreatePostForm() {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
try {
const response = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content }),
});
if (response.ok) {
// 创建成功,重置表单并刷新博客列表
setTitle('');
setContent('');
router.refresh(); // 刷新服务端组件的数据
alert('Post created successfully!');
} else {
const error = await response.json();
alert(`Error: ${error.error}`);
}
} catch (error) {
alert('An error occurred while creating the post.');
} finally {
setIsLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4 p-4 border rounded-lg">
<h2 className="text-2xl font-bold">Create New Post</h2>
<div>
<label htmlFor="title" className="block text-sm font-medium text-gray-700">Title</label>
<input
type="text"
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
/>
</div>
<div>
<label htmlFor="content" className="block text-sm font-medium text-gray-700">Content</label>
<textarea
id="content"
value={content}
onChange={(e) => setContent(e.target.value)}
required
rows={5}
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
/>
</div>
<button
type="submit"
disabled={isLoading}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{isLoading ? 'Creating...' : 'Create Post'}
</button>
</form>
);
}然后,在博客列表页面引入这个表单:
tsx
// app/blog/page.tsx (更新)
import Link from 'next/link';
import { getPosts } from '@/lib/posts';
import { CreatePostForm } from '@/app/components/CreatePostForm';
export default function BlogListPage() {
const posts = getPosts();
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2">
<h1 className="text-3xl font-bold mb-8">My Blog</h1>
<ul className="space-y-4">
{posts.map((post) => (
<li key={post.id} className="border-b pb-4">
<Link href={`/blog/${post.id}`} className="text-xl font-semibold text-blue-600 hover:underline">
{post.title}
</Link>
<p className="text-gray-500 text-sm">{post.date}</p>
<p className="mt-2">{post.content.slice(0, 100)}...</p>
</li>
))}
</ul>
</div>
<div>
<CreatePostForm />
</div>
</div>
);
}现在,你的全栈博客应用就完成了!它具备了显示文章、查看文章详情和创建新文章的功能。
第四部分:精通与优化篇 - 迈向生产环境
4.1 中间件 (Middleware)
中间件允许你在请求完成之前运行代码。它可用于身份验证、日志记录、重写 URL、修改响应头等。
创建一个 middleware.ts 文件在项目根目录(或 src 目录下)。
tsx
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 检查 cookie 或 header 来进行简单的认证
const isLoggedIn = request.cookies.get('auth-token')?.value === 'secret-token';
// 如果用户未登录且试图访问 /admin,重定向到 /login
if (request.nextUrl.pathname.startsWith('/admin') && !isLoggedIn) {
return NextResponse.redirect(new URL('/login', request.url));
}
// 可以修改响应头
const response = NextResponse.next();
response.headers.set('x-custom-header', 'my-value');
return response;
}
// 配置中间件匹配的路径
export const config = {
matcher: '/admin/:path*', // 只为 /admin 下的路径运行中间件
};4.2 图像优化 (Next.js Image Component)
Next.js 提供了一个强大的 <Image> 组件,用于自动优化图片。
优势:
自动优化: 提供 WebP、AVIF 等现代格式。
懒加载: 图片仅在进入视口时加载。
防止布局偏移: 自动设置宽度和高度。
响应式: 配合
sizes属性,提供响应式图片。
tsx
import Image from 'next/image';
export function MyComponent() {
return (
<div>
{/* 本地图片 */}
<Image
src="/me.png" // 位于 public/ 目录
alt="Picture of the author"
width={500} // 必须指定
height={500} // 必须指定
/>
{/* 远程图片 - 需要在 next.config.js 中配置 domains */}
<Image
src="https://example.com/photo.jpg"
alt="A remote image"
width={500}
height={300}
// 其他常用属性
placeholder="blur" // 加载时显示模糊占位图
blurDataURL="data:image/jpeg;base64,..." // 小图的 base64
priority // 优先级加载 (用于 LCP 图片)
/>
</div>
);
}在 next.config.js 中配置允许的图片域名:
javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['example.com', 'images.unsplash.com'],
},
};
module.exports = nextConfig;4.3 静态资源与元数据 (Metadata)
在 App Router 中,你可以通过导出 metadata 对象来定义页面的 SEO 信息。
tsx
// app/blog/[id]/page.tsx (更新)
import { Metadata } from 'next';
// 动态生成元数据
export async function generateMetadata({ params }: BlogPostProps): Promise<Metadata> {
const post = getPostById(params.id);
if (!post) {
return {
title: 'Post Not Found',
};
}
return {
title: post.title,
description: post.content.slice(0, 160), // 用内容前160字符做描述
openGraph: {
title: post.title,
description: post.content.slice(0, 160),
type: 'article',
publishedTime: post.date,
},
};
}
// ... 原有的 page 组件代码4.4 性能优化
使用 next/dynamic 进行动态导入(懒加载):
将非关键的组件拆分成独立的 chunk,在需要时再加载。tsx
// 动态导入一个重量的组件,比如一个图表库 import dynamic from 'next/dynamic'; const HeavyChart = dynamic(() => import('@/app/components/HeavyChart'), { loading: () => <p>Loading Chart...</p>, ssr: false, // 如果组件只在客户端运行,可以禁用 SSR }); export function Dashboard() { return ( <div> <h1>Dashboard</h1> {/* 这个组件只有在渲染时才会被加载 */} <HeavyChart /> </div> ); }分析包大小:
Next.js 内置了分析工具。运行npm run build后,你可以看到每个页面和依赖的分解。bash
npm run build # 输出中会包含页面大小信息
你也可以使用
@next/bundle-analyzer来生成可视化的报告。优化字体:
Next.js 对 Google Fonts 和本地字体有很好的优化支持,能自动处理子集和缓存。tsx
// 在布局或页面中 import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'] }); export default function RootLayout({ children }) { return ( <html lang="en" className={inter.className}> <body>{children}</body> </html> ); }
4.5 部署
Next.js 应用可以部署到任何支持 Node.js 的服务器,但最丝滑的体验是部署到 Vercel(Next.js 的创建者)。
部署到 Vercel:
将你的代码推送到 GitHub、GitLab 或 Bitbucket。
在 Vercel 上注册并连接你的 Git 仓库。
Vercel 会自动检测到是 Next.js 项目,并配置好构建和部署设置。
点击部署。之后,每次向主分支推送代码,都会触发自动部署。
其他部署平台:
Netlify: 同样提供优秀的体验。
AWS / GCP / Azure: 可以使用 Docker 或平台特定的方式部署。
你自己的服务器: 运行
npm run build然后npm start。
总结与展望
恭喜你!通过这篇万字长文,你已经系统地学习了 Next.js 的核心概念和高级特性。
我们从这里出发:
理解了 Next.js 的优势和项目创建。
掌握了 App Router、Server/Client Components、路由、布局和数据获取。
实战构建了一个具备 CRUD 功能的全栈博客。
探索了中间件、图像优化、元数据和性能优化等生产级特性。
下一步学习方向:
状态管理: 在复杂的 Client Components 中,考虑使用 Zustand、Jotai 或 Redux Toolkit。
测试: 学习使用 Jest 和 React Testing Library 为你的组件和页面编写单元测试和集成测试。
数据库集成: 深入学习如何使用 Prisma、Drizzle ORM 或直接使用数据库驱动(如
pgfor PostgreSQL)来持久化数据。认证与授权: 集成 NextAuth.js 或 Auth.js 来处理复杂的用户登录和权限。
持续学习: Next.js 生态在快速发展,关注官方博客和文档,了解最新的特性和最佳实践。
到此这篇关于Next.js从入门到精通的文章就介绍到这了,更多相关Next.js从入门到精通内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
JS将所有对象s的属性复制给对象r(原生js+jquery)
这篇文章主要介绍了js中将所有对象s的属性复制给对象r的方法,原生js+jquery分别实现2014-01-01


最新评论