使用场景
- 必须使用打包程序(例如 webpack)打包代码,并使用 Babel 等编译器进行代码转换。
- 你需要针对生产环境进行优化,例如代码拆分。
- 你可能需要对一些页面进行预先渲染以提高页面性能和 SEO。你可能还希望使用服务器端渲染或客户端渲染。
- 你可能必须编写一些服务器端代码才能将 React 应用程序连接到数据存储。
优势
- 直观的、 基于页面 的路由系统(并支持 动态路由)
- 预渲染。支持在页面级的 静态生成 (SSG) 和 服务器端渲染 (SSR)
- 自动代码拆分,提升页面加载速度
- 具有经过优化的预取功能的 客户端路由
- 内置 CSS 和 Sass 的支持,并支持任何 CSS-in-JS 库
- 开发环境支持 快速刷新
- 利用 Serverless Functions 及 API 路由 构建 API 功能
- 完全可扩展
一. 约定式路由
Next.js 会根据 pages
目录下的文件结构自动生成对应路由
在 Next.js 中,一个页面就是在 pages
目录下被导出的一个 React 组件。
pages
根据其目录下的 文件名与路由进行关联,例如:
pages/index.js
与路由的/
(根路径) 关联pages/post/first-post.js
与路由的/posts/first-post
关联
二. 跳转页面-Link
在 Next.js 中, 我们使用 Link 标签 包裹 a 标签 实现页面跳转
import Link from 'next/link'
Read <Link href="/posts/first-post"><a>this page!</a></Link>
Client-Side Navigation(客户端导航)
Link
组件可在同一 Next.js 应用程序中的两个页面之间启用 client-side navigation
。
client-side navigation
意味着使用 JavaScript 代码 进行页面跳转,这比浏览器执行的默认导航要快。
Link
与 a
标签的区别
<a>
跳转会刷新整个页面, <Link>
则不会
Code splitting and prefetching(代码分割和预加载)
Next.js 会自动进行代码拆分,因此每个页面仅加载该页面所需的内容。 这意味着在呈现主页时,最初不会提供其他页面的代码。
这样可以确保即使你添加数百页也可以快速加载主页。
仅加载你请求的页面的代码也意味着页面被隔离。 如果某个页面抛出错误,则该应用程序的其余部分仍将正常工作。
此外,在 Next.js 的生产版本中,只要 Link
组件出现在浏览器的视口中,Next.js 就会自动在后台预加载接页面的代码。 当你单击链接时,目标页面的代码将已经在后台加载,并且页面转换将很快完成!
总结
Next.js 通过,code splitting(代码分离)
, client-side navigation(客户端导航)
, and prefetching (in production)(预加载)
自动优化你的应用程序以获得最佳性能。
你可以直接在 pages
目录下 创建文件(自动生成路由),并使用内置的 Link
组件。 而不需要其他路由库。
注意: 如果需要链接到 Next.js 应用程序外部的外部页面,需使用 a
Learn <a href="https://www.nextjs.cn">Next.js!</a>
如果你需要添加诸如 className
之类的属性,请将其添加到 a
标签而不是 Link
标签中。 下面一个例子。
// Example: Adding className with <Link>
import Link from "next/link";
export default function LinkClassnameExample() {
// To add attributes like className, target, rel, etc.
// add them to the <a> tag, not to the <Link> tag.
return (
<Link href='/'>
<a className='foo' target='_blank' rel='noopener noreferrer'>
Hello World
</a>
</Link>
);
}
三. 资源、元数据和 CSS
在这一节你可以了解到
- 如何在 Next.js 中添加静态文件(eg:图片)
- 如何为每个页面自定义 head
- 如何 在
pages/_app.js
中添加全局样式 - 如何用 CSS module 构建一个 样式可重用的 React 组件
3.1 Next.js 会把 public 目录当做根路径直接读取静态资源
比如 public
文件夹下有一个 logo.svg 图片 可以像如下使用
<img src='/logo.svg' alt='Vercel Logo' className='logo' />
3.2 自定义文档 head
作用:可以为单页面应用的每一个子页面定义不同的 metaData
, title
, link
等等
<Head>
<title>6666p</title>
<link rel='icon' href='/favicon.ico' />
</Head>
3.3 CSS Styling
Next.js 内部使用一个叫 styled-jsx
的 “CSS-in-JS” 库
它使你可以在 React 组件中编写 CSS ,并且 CSS 样式将受到限制(其他组件不会受到影响)。
Next.js 内置了对 styled-jsx
的支持,但是你也可以使用其他流行的 CSS-in-JS 库,例如 styled-components
。
方式一: CSS-in-JS
<style jsx>{`
…
`}</style>
方式二: 编写和导入 CSS
Next.js 具有对 CSS 和 Sass 的内置支持,你可以直接导入 .css 和 .scss 文件。
在本文中,我们也会讨论如何在 Next.js 中编写和导入 CSS 文件。 我们还将讨论 Next.js 对 CSS 模块和 Sass 的内置支持。
3.4 Layout Component
Layout.js
在 根路径 新建 components
文件夹,进入文件夹, 新建 layout.js
export default function Layout({ children }) {
return <div>{children}</div>;
}
使用方式:
// /pages目录下
import Link from "next/link";
import Head from "next/head";
import Layout from "../../components/layout";
export default function FirstPost() {
return (
<Layout>
<h1>First Post</h1>
</Layout>
);
}
添加 CSS
使用 CSS Modules 的方式 为 react 组件添加样式
Important: To use CSS Modules, the CSS file name must end with .module.css.
在 components
目录下 新建 layout.module.css
文件,内容如下
.container {
max-width: 36rem;
padding: 0 1rem;
margin: 3rem auto 6rem;
}
在 layout.js 中使用
import styles from "./layout.module.css";
export default function Layout({ children }) {
return <div className={styles.container}>{children}</div>;
}
自动生成全局唯一的 className
这就是 CSS Module 所做的:自动生成唯一的类名。 只要使用 CSS Module,就不必担心类名冲突。
此外,Next.js 的代码拆分功能也可以在 CSS 模块上使用。 它确保为每个页面加载最少的 CSS。 这降低代码打包后的大小。
CSS 模块是在构建时从 JavaScript 捆绑包中提取的,并生成.css
文件,这些文件由 Next.js 自动加载。
3.5 全局样式
CSS Module 是应用于组件级别的样式,但是如果你想每个页面都加载一样的 CSS , Next.js 同样支持,
styles/global.css
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
line-height: 1.6;
font-size: 18px;
}
* {
box-sizing: border-box;
}
a {
color: #0070f3;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
max-width: 100%;
display: block;
}
pages/_app.js
在 pages 目录下 创建 _app.js
文件,并加入如下内容
import "../styles/global.css";
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />;
}
四. 预渲染和数据获取
这里你可以了解到
- Next.js 的 预渲染功能
- 预渲染的两种方式/形式: 静态生成, 服务端渲染
- 静态生成 方式 在 两种场景下的使用
- 需要获取外部数据数据-
getStaticProps
- 不需要获取外部数据
- 需要获取外部数据数据-
4.1 Pre-rendering(预渲染)
在讨论数据获取之前,让我们谈谈 Next.js 中最重要的概念之一:预渲染。
默认情况下,Next.js 会预渲染每个页面。
这意味着 Next.js 会预先为每个页面生成 HTML,而不是全部由客户端 JavaScript 完成。
预渲染可以带来更好的性能和 SEO。
每个生成的 HTML 都与该页面所需的最少 JavaScript 代码相关联。 当浏览器加载页面时,其 JavaScript 代码将运行并使页面完全具有交互性。 (此过程称为 hydration。)
4.1.1 检查预渲染是否生效
你可以通过执行以下步骤来检查预渲染是否生效:
- 在浏览器中禁用 JavaScript(在 Chrome 中是这样),然后…
- 刷新浏览器当前页面, 尝试访问此页面。
你应该看到你的应用是在没有 JavaScript 的情况下呈现的。 如果界面展现出较完整的 HTML, 那么说明该网站已将 web 应用预先渲染为静态 HTML,从而使无需运行 JavaScript 即可查看应用界面。
如果你的应用程序是普通的 React.js 应用程序(没有 Next.js),则不会进行预渲染,因此,如果禁用 JavaScript,你将无法看到该 React 应用的正常界面。 例如: 此网站是用普通 React.js 构建的
- 禁用 JavaScript 并刷新访问同一页面。
- 界面显示“你需要启用 JavaScript 才能运行此应用程序。” 这是因为该应用程序未预先呈现为静态 HTML。
4.1.2 总结: 预渲染 VS 没有预渲染(单页面应用-客户端渲染)
访问浏览器之前, 预先渲染出了 HTML。 在浏览器端访问某一个页面时,再通过 JS 绑定 交互事件!
渲染 HTML, 绑定交互事件 都在 浏览器端访问页面时通过 js 进行
4.1.3 预渲染的两种方式
Next.js 具有两种形式的预渲染: 静态生成(Static Generation) 和 服务器端渲染(Server-side Rendering)。
这两种方式的不同之处在于为 page(页面)生成 HTML 页面的 时机 。
在开发环境中(当你 执行
npm run dev
或yarn dev
),每个页面的预渲染形式是 服务端渲染 - 尽管给页面设置了 "静态生成"的方式
重要的是,Next.js 允许你为每个页面 选择 预渲染的方式。你可以创建一个 “混合渲染” 的 Next.js 应用程序:对大多数页面使用“静态生成”,同时对其它页面使用“服务器端渲染”。
4.2 使用静态生成和 服务端渲染的时机
我们建议你尽可能使用 静态生成 ,因为你的所有 page(页面)都可以只构建一次并托管到 CDN 上,这比让服务器根据每个页面请求来渲染页面快得多。 还可以对多种类型的页面使用“静态生成”,包括:
- 营销页面
- 博客文章
- 电商产品列表
- 帮助和文档
你应该问问自己:“我可以在用户请求之前预先渲染此页面吗?” 如果答案是肯定的,则应选择“静态生成”。
另一方面,如果你无法在用户请求之前预渲染页面,则“静态生成” 不是 一个好主意。这也许是因为你的页面需要显示频繁更新的数据,并且页面内容会随着每个请求而变化。
在这种情况下,你可以执行以下任一操作:
- 将“静态生成”与 客户端渲染 一起使用:你可以跳过页面某些部分的预渲染,然后使用客户端 JavaScript 来填充它们。要了解有关此方法的更多信息,请查看 获取数据 章节的文档。
- 使用 服务器端渲染: Next.js 针对每个页面的请求进行预渲染。由于 CDN 无法缓存该页面,因此速度会较慢,但是预渲染的页面将始终是最新的。我们将在下面讨论这种方法。
4.2 以静态生成的方式获取数据(在打包构建时获取数据)
静态生成 可以有数据也可以没有数据。
到目前为止,我们创建的所有页面都不需要提取外部数据。 在为应用程序构建应用程序时,将自动静态生成这些页面。
但是,对于某些页面,你可能必须先获取一些外部数据才能渲染 HTML。 也许你需要在构建时访问文件系统,获取外部 API 或查询数据库。 Next.js 开箱即用地支持这种情况-带有数据的静态生成。
4.2.1 在静态生成的方式下 通过 getStaticProps
获取数据
要在预渲染时获取此数据,Next.js 允许你从同一文件 export(导出)
一个名为 getStaticProps
的 async(异步)
函数。(当你导出一个 React 组件时, 也可以导出一个 getStaticProps 异步函数
)
- 该函数在构建时被调用,
- 并允许你在预渲染时将获取的数据作为
props
参数传递给页面。
function Blog({ posts }) {
// Render posts...
}
// 此函数在构建时被调用
export async function getStaticProps() {
// 调用外部 API 获取博文列表
const res = await fetch("https://.../posts");
const posts = await res.json();
// 通过返回 { props: posts } 对象,Blog 模块
// 在构建时将接收到 `posts` 参数
return {
props: {
posts,
},
};
}
export default Blog;
getStaticProps
函数会告诉 Next.js:“嘿,此页面具有某些数据依赖关系-因此,在构建时预渲染此页面时,请确保先请求数据!”
Note: In development mode,
getStaticProps
runs on each request instead.
4.2.2 添加 接口请求代码
在 pages/index.js
加入以下代码
import Link from "next/link";
import Layout from "../components/layout";
import { getSortedPostsData } from "../lib/posts";
export default function Home({ allPostsData }) {
return (
<Layout home>
<section>
<ul>
{allPostsData.map(({ id, title }) => (
<li key={id}>
<Link href={`/posts/${id}`}>
<a>{title}</a>
</Link>
</li>
))}
</ul>
</section>
</Layout>
);
}
export async function getStaticProps() {
const allPostsData = getSortedPostsData();
return {
props: {
allPostsData,
},
};
}
新建 lib/posts.js
文件,加入如下内容
在 lib/posts.js
中,我们实现了getSortedPostsData
,从其他来源(例如外部 API)获取数据
export async function getSortedPostsData() {
// Instead of the file system,
// fetch post data from an external API endpoint
const res = await fetch("..");
return res.json();
}
注意:Next.js 在客户端和服务器上均会自动导入
fetch()
。 你不需要手动导入它。
除此之外,你还可以直接查询数据库。 因为getStaticProps 仅在服务器端运行。 它永远不会在客户端运行。 它甚至不会包含针对浏览器 打包后的 JS 代码 中。 这意味着你可以编写诸如直接数据库查询之类的代码,而无需将其发送到浏览器。
4.3 开发环境与生产环境的区别
- 在开发环境中(
npm run dev
或yarn dev
),getStaticProps
函数 每次发起请求时都会执行 - 在生产环境中,
getStaticProps
只在构建打包时会执行
由于它是在构建时运行的,因此你将无法使用仅在请求时间内可用的数据,例如 URL 查询参数或 HTTP 头。
4.4 只允许在页面(Page) 内使用
getStaticProps
只能从页面导出。 你无法从非页面文件中导出它。
出现此限制的原因之一是,在渲染页面之前,React 需要拥有所有必需的数据。
4.5 如果我需要在请求时获取数据怎么办?
如果你无法在用户请求之前预渲染页面,则不建议静态生成的方式。 也许你的页面显示了频繁更新的数据,并且页面内容在每次请求时都会更改。 在这种情况下,你可以尝试服务器端渲染或跳过预渲染。 **
4.3 以服务端渲染的方式获取数据(在每次请求时获取数据)
如果你不想在 构建时,而想在请求页面时获取数据, 你可以使用** 服务端渲染**
使用 服务端渲染,你需要在页面中 导出一个 getServerSideProps
export async function getServerSideProps(context) {
return {
props: {
// props for your component
},
};
}
因为 getServerSideProps 只在请求被执行,因此他的 参数(context)中 可以取到一些 请求参数
getServerSideProps 使用的时机
使用场景: 必须请求到数据后,再进行预渲染.
到第一个字节(TTFB)的时间将比getStaticProps
慢,因为服务器必须在每个请求上计算结果,并且如果没有额外的配置,则 CDN 不能缓存结果。
4.4 客户端渲染
如果你不需要在预渲染之前获取数据, 你可以使用客户端渲染,
静态生成(预渲染)页面的不需要外部数据的部分。 页面加载后,使用 JavaScript 从客户端获取外部数据并填充其余部分。
例如,此方法适用于用户仪表板页面。 由于信息中心是一个私有的,特定于用户的页面,因此 SEO 无关紧要,并且该页面无需预先呈现。 数据经常更新,这需要获取请求时数据。
4.5 SWR
Next.js 背后的团队创建了一个名为SWR 的 React Hook 来进行数据获取。 如果你要在客户端获取数据,我们强烈建议你这样做。 它处理缓存,重新验证,焦点跟踪,间隔重新获取等等。 我们不会在这里介绍详细信息,但是这里是一个示例用法:
import useSWR from "swr";
function Profile() {
const { data, error } = useSWR("/api/user", fetch);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}