Next.js
简介
Next.js 是一个基于 React 的开源框架,用于构建快速、现代化的 Web 应用程序。
Next.js 用于构建高性能的服务器端渲染(SSR)和静态生成(Static Generation)的现代 Web 应用。
主要特性
文件系统路由: Next.js 使用文件系统来自动化路由的创建。你只需要在 pages 目录下创建文件,它就会自动映射为相应的路由,不需要额外的路由配置。
静态生成(SSG)与服务端渲染(SSR): Next.js 支持这两种渲染方式,可以根据需要灵活选择。静态生成适用于大多数情况,尤其是内容不会频繁变化的页面,而 SSR 适用于需要动态获取数据的页面。
API 路由: Next.js 允许你在应用中直接创建 API 路由,可以在 pages/api 目录下轻松创建后端 API 端点,处理前后端逻辑。
自动代码拆分: 每个页面只会加载它所需的 JavaScript 代码,确保应用启动速度更快,减少不必要的资源消耗。
优化图片: Next.js 内置了图片优化功能,使用 next/image 组件可以自动为图像选择最佳格式、压缩、懒加载等,以提升页面加载性能。
支持 TypeScript: Next.js 默认支持 TypeScript,可以让开发者在开发过程中享受更强的类型检查。
安装
确保安装了node和npm
node -v
npm -v
手动安装
npm install next@latest react@latest react-dom@latest
然后打开package.json文件,添加下面内容
{
"scripts": {
"dev": "next dev", // 以开发模式启动 Next.js
"build": "next build", // 构建应用以供生产使用
"start": "next start", // 启动 Next.js 生产环境服务器
"lint": "next lint" // 配置 Next.js 的内置 ESLint
}
}
然后,创建一个 app 文件夹,然后添加一个 layout.tsx 和一个 page.tsx 文件。当用户访问应用根路径 / 时,这些文件会被渲染。
根布局文件 layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
首页文件 page.tsx
export default function Page() {
return <h1>Hello, Next.js!</h1>
}
注意事项
如果你忘记创建 layout.tsx,Next.js 会在运行开发服务器(next dev)时自动创建这个文件。
你可以选择性地在项目根目录中使用 src 目录,将应用代码与配置文件分开。
如果需要存储静态资源,如图片、字体等,你可以选择在项目根目录创建一个 public 文件夹。public 文件夹中的文件可以通过 / 路径来引用。
运行。默认运行在 3000 端口。访问 http://localhost:3000 查看应用。
npm run dev
npx安装(推荐)
npx create-next-app@latest my-next-app
npx 是一个 Node.js 工具,用于运行包中的二进制文件,而无需全局安装。create-next-app 是脚手架工具。my-next-app 是项目的名字,你可以根据需要修改为任何名称。执行命令后会自动安装项目所需的依赖。自动完成的操作有:
- 创建一个名为 my-next-app 的文件夹。
- 初始化项目,安装必要的依赖。
- 创建基本的项目结构,包括 app 文件夹、public 文件夹、package.json 文件等。
目录结构
- app/ 目录用于存放页面和布局文件,是 Next.js 中的核心目录。
- public/ 用于存放公开的静态资源文件。
- styles/ 用于存放样式文件。
- node_modules/ 存放项目的所有依赖包。
- package.json 是项目的配置文件,包含项目依赖和脚本命令。
- next.config.js 用于定制 Next.js 的配置。
app 目录
app/
├── layout.tsx # 根布局文件
├── page.tsx # 根页面
└── [slug]/ # 动态路由目录
├── page.tsx # 动态页面
└── layout.tsx # 特定页面的布局
- layout.tsx:在这个文件中定义了根布局或页面布局,通常用于设置全局结构(如
<html>和<body>标签)。所有子页面都会被嵌套在这个布局内。 - page.tsx:每个页面组件都应该有一个
page.tsx文件。这个文件代表该路由对应的页面内容。例如,app/page.tsx对应的是根路径 (/) 的页面。 - 动态路由:
Next.js支持动态路由,动态路由目录的名称用方括号包围,如[slug]。这意味着你可以根据 URL 中的不同部分加载不同的页面。例如,[slug]/page.tsx会根据 slug 参数动态渲染页面。
public 目录
public/
├── images/ # 存放图片文件
├── fonts/ # 存放字体文件
└── favicon.ico # 网站图标
文件可以通过 /images/ 等路径直接访问。例如,public/images/logo.png 可以通过 http://localhost:3000/images/logo.png 访问。
style 目录
styles/
├── Home.module.css # CSS 模块化样式
└── globals.css # 全局样式
- Home.module.css:这是一个 CSS 模块文件,默认情况下,Next.js 会自动为每个组件使用 CSS 模块,确保样式作用域仅限于该组件。
- globals.css:存放全局样式,通常用于设置全局字体、颜色等。
项目结构目录
my-next-app/
├── node_modules/ # 项目依赖的第三方库
├── public/ # 静态资源文件夹
│ ├── favicon.ico # 网站图标
│ └── ... # 其他静态资源(如图片、字体等)
├── app/ # 应用路由目录(核心)
│ ├── layout.js # 根布局组件
│ ├── page.js # 首页组件
│ ├── loading.js # 页面加载时显示加载状态
│ ├── error.js # 捕获并显示错误
│ ├── not-found.js # 404页面
│ ├── about/ # 关于页面
│ │ └── page.js # 关于页面组件
│ ├── blog/ # 博客页面
│ │ ├── page.js # 博客列表页
│ │ └── [slug]/ # 动态路由
│ │ └── page.js # 博客详情页
│ └── ... # 其他页面和路由
├── components/ # 可复用的 React 组件
├── styles/ # 样式文件
├── utils/ # 工具函数
├── package.json # 项目配置和依赖管理
├── package-lock.json # 依赖的精确版本锁定文件
├── next.config.js # Next.js 配置文件
└── README.md # 项目说明文档
app 目录与传统 pages 目录的对比
Next.js 13 引入了新的 app 目录,它与传统的 pages 目录共存,并提供了更灵活的路由和布局组织方式。
| 特性 | pages 目录 | app 目录 |
|---|---|---|
| 路由定义 | 文件系统路由 | 文件夹路由 |
| 布局管理 | 手动实现 | 嵌套布局 |
| 数据获取 | getStaticProps 等 | Server Components + async/await |
| 文件命名 | 自由命名 | 必须命名为 page.js |
| 特殊文件 | _app.js, _document.js 等 | layout.js, loading.js 等 |
| 性能优化 | 手动优化 | 默认优化 |
| 兼容性 | 完全兼容 | Beta 阶段,需手动启用 |
新特性
- 服务器组件
Next.js 13 默认使用服务器组件,减少了客户端 JavaScript 的加载量
该种组件可以直接访问后端数据
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
export default async function Page() {
const data = await getData();
return <div>{data.message}</div>;
}
- 客户端组件
需要使用 React 状态 或浏览器API,可将组件标记为客户端组件
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
- 流式渲染
页面加载时逐步渲染内容
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<Suspense fallback={<p>Loading...</p>}>
<SlowComponent />
</Suspense>
</div>
);
}
数据获取
允许在不同渲染阶段获取和展示数据。
- 从客户端获取
用 React 的 useEffect 钩子
// app/posts/page.tsx
'use client';
import { useEffect, useState } from 'react';
export default function PostsPage() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await res.json();
setPosts(data);
};
fetchPosts();
}, []);
return (
<div>
<h1>RUNOOB 测试</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
useEffect 会在客户端渲染后执行,适用于客户端渲染的数据。
在客户端获取数据,适合不需要在服务端渲染的内容。
可以根据用户交互或时间等因素动态获取数据。
使用场景:
动态加载的数据,比如无限滚动或分页。
用户操作后才获取的数据,比如点击按钮后获取数据。
- 服务端数据获取
两种方式,fetch API和 ORM 或数据库
- 用 fetch API
// app/posts/page.tsx
export default async function Page() {
const data = await fetch('https://jsonplaceholder.typicode.com/posts')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
- 用 ORM 或 数据库
// app/posts/page.tsx
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
路由
路由
app/
├── page.tsx # 对应根路径 /
├── about/
│ └── page.tsx # 对应 /about
├── [slug]/
│ └── page.tsx # 对应动态路径,如 /[slug]
获取动态路由参数
'use client';
import { useParams } from 'next/navigation';
export default function Post() {
const params = useParams();
const { id } = params; // 获取动态路由参数 `id`
return <div>Post ID: {id}</div>;
}
路由导航
import Link from 'next/link';
export default function HomePage() {
return (
<div>
<h1>Welcome to Next.js!</h1>
<Link href="/about">About Us</Link>
</div>
);
}
CSS 样式
全局样式影响整个应用的 CSS 样式,它通常用于设置页面的基础样式,如字体、颜色、布局等。
Next.js 通过 app/layout.tsx 中引入全局 CSS 文件。
默认情况下 app 目录已经有了全局样式 globals.css 文件,如果不存在,可以在 styles 目录下创建globals.css 文件,然后在 app/layout.tsx 中引入。
// app/layout.tsx
// 这些样式会应用到应用中的每个路由
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>{children}</body>
</html>
)
}
支持 Sass
配置 Sass 的选项,可以在 next.config.js 中使用 sassOptions 选项
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
sassOptions: {
additionalData: `$var: red;`, // 在每个 Sass 文件中自动注入这些样式
},
}
export default nextConfig
通过这种方式,你可以配置一些默认的 Sass 数据,或者进行其他自定义设置。
创建一个 SCSS 文件,如 styles/Button.module.scss:
/* styles/Button.module.scss */
.button {
padding: 10px 20px;
background-color: #0070f3;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
组件中使用
// app/components/Button.tsx
import styles from '../styles/Button.module.scss';
export default function Button() {
return <button className={styles.button}>Click Me</button>;
}
安装支持 Tailwind CSS
- 安装
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
- 配置
一般有已配置好的 tailwind.config.ts 文件
import type { Config } from "tailwindcss";
export default {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}", // 注意添加了 app 目录。
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
plugins: [],
} satisfies Config;
postcss.config.mjs 文件也无需修改。
- 添加基础样式
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
根布局 app/layout.tsx 中导入 globals.css 样式表
// app/layout.tsx
// 这些样式会应用到应用中的每个路由
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>{children}</body>
</html>
)
}
图片与字体
nextjs提供了 Image 组件,优化图片加载,扩展了HTML的img元素,提供以下功能:
-
尺寸优化,自动为不同设备提供正确尺寸的图片,支持现代图片格式(如 WebP)。
-
视觉稳定性:防止图片加载时的布局偏移(Layout Shift)。
-
更快加载:使用原生浏览器懒加载,仅在图片进入视口时加载。
-
资源灵活性:支持按需调整图片尺寸,即使是远程图片。
使用 Image 组件
import Image from 'next/image';
export default function Page() {
return <Image src="/images/logo.png" alt="Logo" width={500} height={300} />;
}
本地图片与远程图片
import Image from 'next/image';
import profilePic from './me.png';
export default function Page() {
return (
<Image
src={profilePic}
alt="Picture of the author"
// width 和 height 会自动从图片文件中获取
// blurDataURL 和 placeholder 可选
/>
);
}
// 远程图片要指定 width 和 height,避免布局偏移
export default function Page2() {
return (
<Image
src="https://s3.amazonaws.com/my-bucket/profile.png"
alt="Picture of the author"
width={500}
height={500}
/>
);
}
配置远程图片
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/my-bucket/**',
},
],
},
};
export default nextConfig;
字体优化
Next.js 提供了 next/font 模块,用于优化字体加载。它支持自动托管字体文件,提升隐私和性能。
import { Geist } from 'next/font/google';
const geist = Geist({
subsets: ['latin'],
});
export default function Layout({ children }) {
return (
<html lang="en" className={geist.className}>
<body>{children}</body>
</html>
);
}
如果使用非可变字体,需要指定 weight
import { Roboto } from 'next/font/google';
const roboto = Roboto({
weight: '400',
subsets: ['latin'],
});
export default function Layout({ children }) {
return (
<html lang="en" className={roboto.className}>
<body>{children}</body>
</html>
);
}
使用本地字体
import localFont from 'next/font/local';
const myFont = localFont({
src: './my-font.woff2',
});
export default function Layout({ children }) {
return (
<html lang="en" className={myFont.className}>
<body>{children}</body>
</html>
);
}
使用多个字体文件
const roboto = localFont({
src: [
{
path: './Roboto-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './Roboto-Italic.woff2',
weight: '400',
style: 'italic',
},
{
path: './Roboto-Bold.woff2',
weight: '700',
style: 'normal',
},
{
path: './Roboto-BoldItalic.woff2',
weight: '700',
style: 'italic',
},
],
});
组件与布局
组件,可以是页面组件、UI组件或功能组件。
// 页面组件 pages/about.js 或 app/about/page.js
export default function About() {
return <div>About Page</div>;
}
// UI组件
// components/Button.js
export default function Button({ children }) {
return <button>{children}</button>;
}
// 功能组件实现特定功能的组件,例如数据获取、状态管理等。通常存放在 hooks 或 utils 目录下。
// hooks/useData.js
import { useEffect, useState } from 'react';
export default function useData(url) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((data) => setData(data));
}, [url]);
return data;
}
布局,用于定义页面结构的组件,通常包含页眉、页脚、导航栏等公共元素。
全局布局
// pages/_app.js 或 app/layout.js
import Header from '../components/Header';
import Footer from '../components/Footer';
export default function Layout({ children }) {
return (
<div>
<Header />
<main>{children}</main>
<Footer />
</div>
);
}
嵌套布局
app/
├── layout.js // 根布局
├── about/
│ ├── layout.js // /about 的布局
│ └── page.js // /about 页面
└── dashboard/
├── layout.js // /dashboard 的布局
└── page.js // /dashboard 页面
// app/dashboard/layout.js
export default function DashboardLayout({ children }) {
return (
<div>
<h1>Dashboard Layout</h1>
{children}
</div>
);
}
动态布局允许根据条件动态选择布局。
// app/layout.js
export default function Layout({ children, isDashboard }) {
return (
<div>
{isDashboard ? <DashboardLayout>{children}</DashboardLayout> : <MainLayout>{children}</MainLayout>}
</div>
);
}
旧版本中的特殊文件
- _app.js, 全局组件,包装所有页面组件
- _document.js,自定义HTML文档结构
- _error.js,自定义错误页面
// pages/_app.js
import Layout from '../components/Layout';
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
// pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
// pages/_error.js
export default function Error({ statusCode }) {
return (
<div>
<h1>{statusCode} Error</h1>
<p>Sorry, something went wrong.</p>
</div>
);
}