next.js
I wrote this tutorial to help you quickly learn Next.js and get familiar with how it works.
我编写本教程是为了帮助您快速学习Next.js并熟悉其工作方式。
It's ideal for you if you have zero to little knowledge of Next.js, you have used React in the past, and you are looking forward diving more into the React ecosystem, in particular server-side rendering.
如果您对Next.js的知识从零到零,或者您过去曾经使用过React,并且希望进一步深入到React生态系统,尤其是服务器端渲染,那么它是您的理想选择。
I find Next.js an awesome tool to create Web Applications, and at the end of this post I hope you'll be as excited about it as I am. And I hope it will help you learn Next.js!
我发现Next.js是创建Web应用程序的绝佳工具,并且在本文结尾,我希望您能像我一样对它感到兴奋。 我希望它能帮助您学习Next.js!
Note: you can download a PDF / ePub / Mobi version of this tutorial so you can read it offline!
注意:您可以下载本教程的PDF / ePub / Mobi版本,以便离线阅读 !
Introduction
介绍
The main features provided by Next.js
Next.js提供的主要功能
Next.js vs Gatsby vs create-react-app
Next.js vs Gatsby vs create-react-app
How to install Next.js
如何安装Next.js
View source to confirm SSR is working
查看源代码以确认SSR是否正常运行
The app bundles
该应用程序捆绑
What's that icon in the bottom right?
右下角的图标是什么?
Install the React DevTools
安装React DevTools
Other debugging techniques you can use
您可以使用的其他调试技术
Adding a second page to the site
在网站上添加第二页
Linking the two pages
链接两个页面
Dynamic content with the router
路由器的动态内容
Prefetching
预取
Using the router to detect the active link
使用路由器检测活动链接
Using next/router
使用next/router
Feed data to the components using getInitialProps()
使用getInitialProps()
数据馈送到组件
CSS
CSS
Populating the head tag with custom tags
用自定义标签填充head标签
Adding a wrapper component
添加包装器组件
API routes
API路线
Run code on the server side, or on the client side
在服务器端或客户端上运行代码
Deploying the production version
部署生产版本
Deploying on Now
立即部署
Analyzing the app bundles
分析应用捆绑
Lazy loading modules
延迟加载模块
Where to go from here
从这往哪儿走
Working on a modern JavaScript application powered by React is awesome until you realize that there are a couple problems related to rendering all the content on the client-side.
直到您意识到存在与在客户端呈现所有内容有关的几个问题之前,在React所支持的现代JavaScript应用程序上的工作很棒。
First, the page takes longer to become visible to the user, because before the content loads, all the JavaScript must load, and your application needs to run to determine what to show on the page.
首先,页面需要更长的时间才能被用户看到,因为在加载内容之前,必须加载所有JavaScript,并且您的应用程序需要运行才能确定在页面上显示的内容。
Second, if you are building a publicly available website, you have a content SEO issue. Search engines are getting better at running and indexing JavaScript apps, but it's much better if we can send them content instead of letting them figure it out.
其次,如果您要建立一个公开可用的网站,则存在内容SEO问题。 搜索引擎在运行和索引JavaScript应用方面越来越好,但是如果我们可以向他们发送内容而不是让他们弄清楚,那就更好了。
The solution to both of those problems is server rendering, also called static pre-rendering.
解决这两个问题的方法是服务器渲染 ,也称为静态预渲染 。
Next.js is one React framework to do all of this in a very simple way, but it's not limited to this. It's advertised by its creators as a zero-configuration, single-command toolchain for React apps.
Next.js是一个React框架,可以通过非常简单的方式完成所有这些操作,但不仅限于此。 它的创建者宣传它是React应用程序的零配置单命令工具链 。
It provides a common structure that allows you to easily build a frontend React application, and transparently handles server-side rendering for you.
它提供了一种通用结构,可让您轻松构建前端React应用程序,并透明地为您处理服务器端渲染。
Here is a non-exhaustive list of the main Next.js features:
这是Next.js主要功能的详尽列表:
Next.js reloads the page when it detects any change saved to disk.
当Next.js检测到任何保存到磁盘的更改时,它将重新加载页面。
Any URL is mapped to the filesystem, to files put in the pages
folder, and you don't need any configuration (you have customization options of course).
任何URL都映射到文件系统, pages
文件夹中放置的文件,并且您不需要任何配置(当然,您有自定义选项)。
Using styled-jsx
, completely integrated as built by the same team, it's trivial to add styles scoped to the component.
使用由同一团队完全集成的styled-jsx
,添加范围限定于组件的样式很简单。
You can render React components on the server side, before sending the HTML to the client.
您可以在将HTML发送给客户端之前,在服务器端渲染React组件。
Next.js plays well with the rest of the JavaScript, Node, and React ecosystem.
Next.js在其余JavaScript,Node和React生态系统中表现良好。
Pages are rendered with just the libraries and JavaScript that they need, no more. Instead of generating one single JavaScript file containing all the app code, the app is broken up automatically by Next.js in several different resources.
页面仅使用它们所需的库和JavaScript呈现。 Next.js会在几种不同的资源中自动将应用程序分解,而不是生成包含所有应用程序代码的单个JavaScript文件。
Loading a page only loads the JavaScript necessary for that particular page.
加载页面仅加载该特定页面所需JavaScript。
Next.js does that by analyzing the resources imported.
Next.js通过分析导入的资源来做到这一点。
If only one of your pages imports the Axios library, for example, that specific page will include the library in its bundle.
例如,如果只有一个页面导入Axios库,则该特定页面将在其捆绑包中包含该库。
This ensures your first page load is as fast as it can be, and only future page loads (if they will ever be triggered) will send the JavaScript needed to the client.
这样可以确保您的第一页加载尽可能快,并且只有将来的页面加载(如果会被触发)才会将所需JavaScript发送给客户端。
There is one notable exception. Frequently used imports are moved into the main JavaScript bundle if they are used in at least half of the site pages.
有一个值得注意的例外。 如果至少在网站页面的一半中使用了常用导入,则这些导入将移入JavaScript主捆绑包中。
The Link
component, used to link together different pages, supports a prefetch
prop which automatically prefetches page resources (including code missing due to code splitting) in the background.
用于将不同页面链接在一起的Link
组件支持prefetch
道具,该道具可在后台自动预取页面资源(包括由于代码拆分而丢失的代码)。
You can import JavaScript modules and React Components dynamically.
您可以动态导入JavaScript模块和React组件。
Using the next export
command, Next.js allows you to export a fully static site from your app.
使用next export
命令,Next.js允许您从应用程序中导出一个完全静态的网站。
Next.js is written in TypeScript and as such comes with an excellent TypeScript support.
Next.js用TypeScript编写,因此具有出色的TypeScript支持。
create-react-app
(Next.js vs Gatsby vs create-react-app
)Next.js, Gatsby, and create-react-app
are amazing tools we can use to power our applications.
Next.js, Gatsby和create-react-app
是令人惊奇的工具,我们可以使用它们来为应用程序提供动力。
Let's first say what they have in common. They all have React under the hood, powering the entire development experience. They also abstract webpack and all those low level things that we used to configure manually in the good old days.
让我们先说说它们的共同点。 他们全都拥有React在幕后,为整个开发经验提供动力。 他们还抽象化了webpack以及我们过去在过去曾经手动配置的所有低级内容。
create-react-app
does not help you generate a server-side-rendered app easily. Anything that comes with it (SEO, speed...) is only provided by tools like Next.js and Gatsby.
create-react-app
无法帮助您轻松生成服务器端渲染的应用程序。 它附带的所有内容(SEO,速度...)仅由Next.js和Gatsby之类的工具提供。
When is Next.js better than Gatsby?
什么时候Next.js比Gatsby更好?
They can both help with server-side rendering, but in 2 different ways.
它们都可以帮助服务器端呈现 ,但是有2种不同的方式。
The end result using Gatsby is a static site generator, without a server. You build the site, and then you deploy the result of the build process statically on Netlify or another static hosting site.
使用Gatsby的最终结果是没有服务器的静态站点生成器。 您生成站点,然后在Netlify或另一个静态托管站点上静态部署生成过程的结果。
Next.js provides a backend that can server side render a response to request, allowing you to create a dynamic website, which means you will deploy it on a platform that can run Node.js.
Next.js提供了一个后端,可以在服务器端呈现对请求的响应,从而允许您创建一个动态网站,这意味着您将其部署在可以运行Node.js的平台上。
Next.js can generate a static site too, but I would not say it's its main use case.
Next.js 也可以生成一个静态站点,但是我不会说这是它的主要用例。
If my goal was to build a static site, I'd have a hard time choosing and perhaps Gatsby has a better ecosystem of plugins, including many for blogging in particular.
如果我的目标是建立一个静态站点,那么我将很难选择,也许盖茨比拥有一个更好的插件生态系统,其中包括许多用于博客的插件。
Gatsby is also heavily based on GraphQL, something you might really like or dislike depending on your opinions and needs.
Gatsby很大程度上也是基于GraphQL的 ,根据您的意见和需求,您可能会真正喜欢或不喜欢它。
To install Next.js, you need to have Node.js installed.
要安装Next.js,您需要安装Node.js。
Make sure that you have the latest version of Node. Check with running node -v
in your terminal, and compare it to the latest LTS version listed on https://nodejs.org/.
确保您具有最新版本的Node。 检查终端中正在运行的node -v
,并将其与https://nodejs.org/上列出的最新LTS版本进行比较。
After you install Node.js, you will have the npm
command available into your command line.
安装Node.js之后,您将在命令行中使用npm
命令。
If you have any trouble at this stage, I recommend the following tutorials I wrote for you:
如果您在此阶段遇到任何麻烦,建议您为我编写以下教程:
How to install Node.js
如何安装Node.js
How to update Node.js
如何更新Node.js
An introduction to the npm package manager
npm软件包管理器简介
Unix Shells Tutorial
Unix Shell教程
How to use the macOS terminal
如何使用macOS终端
The Bash Shell
重击壳
Now that you have Node, updated to the latest version, and npm
, we're set!
现在您已经拥有Node(已更新到最新版本)和npm
,我们就可以开始了!
We can choose 2 routes now: using create-next-app
or the classic approach which involves installing and setting up a Next app manually.
我们现在可以选择2条路线:使用create-next-app
或经典方法,其中涉及手动安装和设置Next应用程序。
If you're familiar with create-react-app
, create-next-app
is the same thing - except it creates a Next app instead of a React app, as the name implies.
如果您熟悉create-react-app
,则create-next-app
是同一件事-顾名思义,它创建的是Next应用程序而不是React应用程序。
I assume you have already installed Node.js, which, from version 5.2 (2+ years ago at the time of writing), comes with the npx
command bundled. This handy tool lets us download and execute a JavaScript command, and we'll use it like this:
我假设您已经安装了Node.js,该版本从5.2版(撰写本文时已经2年多)开始捆绑了npx
命令 。 这个方便的工具使我们能够下载并执行JavaScript命令,并按以下方式使用它:
npx create-next-app
The command asks the application name (and creates a new folder for you with that name), then downloads all the packages it needs (react
, react-dom
, next
), sets the package.json
to:
该命令询问应用程序名称(并使用该名称为您创建一个新文件夹),然后下载其所需的所有软件包( react
, react-dom
, next
),并将package.json
设置为:
and you can immediately run the sample app by running npm run dev
:
您可以通过运行npm run dev
立即运行示例应用程序:
And here's the result on http://localhost:3000:
这是http:// localhost:3000上的结果:
This is the recommended way to start a Next.js application, as it gives you structure and sample code to play with. There's more than just that default sample application; you can use any of the examples stored at https://github.com/zeit/next.js/tree/canary/examples using the --example
option. For example try:
这是启动Next.js应用程序的推荐方法,因为它为您提供了结构和示例代码。 不仅仅是默认的示例应用程序; 您可以使用--example
选项使用存储在https://github.com/zeit/next.js/tree/canary/examples中的任何示例。 例如,尝试:
npx create-next-app --example blog-starter
Which gives you an immediately usable blog instance with syntax highlighting too:
这也为您提供了一个立即可用的博客实例,并且语法突出显示了:
You can avoid create-next-app
if you feel like creating a Next app from scratch. Here's how: create an empty folder anywhere you like, for example in your home folder, and go into it:
如果您想从头开始创建Next应用,则可以避免使用create-next-app
。 方法如下:在您喜欢的任何位置(例如在主文件夹中)创建一个空文件夹,然后进入该文件夹:
mkdir nextjs
cd nextjs
and create your first Next project directory:
并创建您的第一个Next项目目录:
mkdir firstproject
cd firstproject
Now use the npm
command to initialize it as a Node project:
现在,使用npm
命令将其初始化为Node项目:
npm init -y
The -y
option tells npm
to use the default settings for a project, populating a sample package.json
file.
-y
选项告诉npm
为项目使用默认设置,并填充示例package.json
文件。
Now install Next and React:
现在安装Next和React:
npm install next react react-dom
Your project folder should now have 2 files:
您的项目文件夹现在应具有2个文件:
package.json
(see my tutorial on it)
package.json
( 请参阅我的教程 )
package-lock.json
(see my tutorial on package-lock)
package-lock.json
( 请参阅我的package-lock教程 )
and the node_modules
folder.
和node_modules
文件夹。
Open the project folder using your favorite editor. My favorite editor is VS Code. If you have that installed, you can run code .
in your terminal to open the current folder in the editor (if the command does not work for you, see this)
使用您喜欢的编辑器打开项目文件夹。 我最喜欢的编辑器是VS Code 。 如果已安装,则可以运行code .
在终端中打开编辑器中的当前文件夹(如果该命令对您不起作用,请参阅此 )
Open package.json
, which now has this content:
打开package.json
,现在具有以下内容:
{
"name": "firstproject",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"next": "^9.1.2",
"react": "^16.11.0",
"react-dom": "^16.11.0"
}
}
and replace the scripts
section with:
并将scripts
部分替换为:
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
to add the Next.js build commands, which we're going to use soon.
添加Next.js构建命令,我们将很快使用它。
Tip: use "dev": "next -p 3001",
to change the port and run, in this example, on port 3001.
提示:使用"dev": "next -p 3001",
来更改端口并在此示例中在端口3001上运行。
Now create a pages
folder, and add an index.js
file.
现在创建一个pages
文件夹,并添加一个index.js
文件。
In this file, let's create our first React component.
在这个文件中,让我们创建第一个React组件。
We're going to use it as the default export:
我们将使用它作为默认导出:
const Index = () => (
Home page
)
export default Index
Now using the terminal, run npm run dev
to start the Next development server.
现在使用终端,运行npm run dev
启动Next开发服务器。
This will make the app available on port 3000, on localhost.
这将使该应用程序在本地主机上的端口3000上可用。
Open http://localhost:3000 in your browser to see it.
在浏览器中打开http:// localhost:3000进行查看。
Let's now check the application is working as we expect it to work. It's a Next.js app, so it should be server side rendered.
现在让我们检查应用程序是否正常运行。 这是一个Next.js应用程序,因此应该在服务器端呈现 。
It's one of the main selling points of Next.js: if we create a site using Next.js, the site pages are rendered on the server, which delivers HTML to the browser.
这是Next.js的主要卖点之一:如果我们使用Next.js创建一个站点,则站点页面将呈现在服务器上,该服务器会将HTML传递给浏览器。
This has 3 major benefits:
这具有3个主要优点:
Let's view the source of the app.Using Chrome you can right-click anywhere in the page, and press View Page Source.
让我们查看应用程序的源代码。使用Chrome,您可以右键单击页面中的任意位置,然后按查看页面源代码 。
If you view the source of the page, you'll see the
snippet in the HTML Home page
body
, along with a bunch of JavaScript files - the app bundles.
如果查看页面的源代码,您将在HTML body
看到
片段,以及一堆JavaScript文件-该应用程序捆绑。 Home page
We don't need to set up anything, SSR (server-side rendering) is already working for us.
我们不需要进行任何设置,SSR(服务器端渲染)已经在为我们工作。
The React app will be launched on the client, and will be the one powering interactions like clicking a link, using client-side rendering. But reloading a page will re-load it from the server. And using Next.js there should be no difference in the result inside the browser - a server-rendered page should look exactly like a client-rendered page.
React应用程序将在客户端上启动,并且将成为使用客户端渲染来推动诸如单击链接之类的交互的一种方式。 但是重新加载页面会从服务器重新加载页面。 使用Next.js,浏览器内部的结果应该没有差异-服务器呈现的页面应该看起来完全像客户端呈现的页面。
When we viewed the page source, we saw a bunch of JavaScript files being loaded:
当查看页面源代码时,我们看到一堆JavaScript文件正在加载:
Let's start by putting the code in an HTML formatter to get it formatted better, so we humans can get a better chance at understanding it:
让我们开始将代码放入HTML格式化程序中,以使其格式更好,以便我们人类可以更好地了解它:
Home page
We have 4 JavaScript files being declared to be preloaded in the head
, using rel="preload" as="script"
:
我们使用rel="preload" as="script"
将4个JavaScript文件声明为要在head
rel="preload" as="script"
:
/_next/static/development/pages/index.js
(96 LOC)
/_next/static/development/pages/index.js
LOC)
/_next/static/development/pages/_app.js
(5900 LOC)
/_next/static/development/pages/_app.js
LOC)
/_next/static/runtime/webpack.js
(939 LOC)
/_next/static/runtime/webpack.js
LOC)
/_next/static/runtime/main.js
(12k LOC)
/_next/static/runtime/main.js
LOC)
This tells the browser to start loading those files as soon as possible, before the normal rendering flow starts. Without those, scripts would be loaded with an additional delay, and this improves the page loading performance.
这告诉浏览器在正常渲染流程开始之前尽快开始加载这些文件。 没有这些脚本,脚本将被额外加载,这将提高页面加载性能。
Then those 4 files are loaded at the end of the body
, along with /_next/static/development/dll/dll_01ec57fc9b90d43b98a8.js
(31k LOC), and a JSON snippet that sets some defaults for the page data:
然后将这4个文件以及/_next/static/development/dll/dll_01ec57fc9b90d43b98a8.js
LOC)和一个设置了页面数据某些默认值的JSON代码片段加载到body
的末尾:
The 4 bundle files loaded are already implementing one feature called code splitting. The index.js
file provides the code needed for the index
component, which serves the /
route, and if we had more pages we'd have more bundles for each page, which will then only be loaded if needed - to provide a more performant load time for the page.
加载的4个捆绑包文件已经实现了一种称为代码拆分的功能。 index.js
文件提供了index
组件所需的代码(用于/
路由),如果我们有更多的页面,我们将为每个页面提供更多的包,然后仅在需要时才加载它们-以提供更高的性能页面的加载时间。
Did you see that little icon at the bottom right of the page, which looks like a lightning?
您是否在页面右下方看到了一个小图标,看起来像闪电?
If you hover it, it's going to say "Prerendered Page":
如果将其悬停,它将显示“ Prerendered Page”:
This icon, which is only visible in development mode of course, tells you the page qualifies for automatic static optimization, which basically means that it does not depend on data that needs to be fetched at invokation time, and it can be prerendered and built as a static HTML file at build time (when we run npm run build
).
该图标( 仅在开发模式下才可见)告诉您该页面符合自动静态优化的条件,这基本上意味着该页面不依赖于需要在调用时获取的数据,并且可以按以下方式进行预渲染和构建:在构建时(当我们运行npm run build
)的静态HTML文件。
Next can determine this by the absence of the getInitialProps()
method attached to the page component.
Next可以通过缺少附加到页面组件的getInitialProps()
方法来确定这一点。
When this is the case, our page can be even faster because it will be served statically as an HTML file rather than going through the Node.js server that generates the HTML output.
在这种情况下,我们的页面甚至可以更快,因为它将作为HTML文件静态提供,而不是通过生成HTML输出的Node.js服务器提供。
Another useful icon that might appear next to it, or instead of it on non-prerendered pages, is a little animated triangle:
可能会显示在它旁边的另一个有用的图标,或者是在非呈递页面上代替它的是一个动画三角形:
This is a compilation indicator, and appears when you save a page and Next.js is compiling the application before hot code reloading kicks in to reload the code in the application automatically.
这是一个编译指示符,当您保存页面并且Next.js正在编译应用程序之前,此提示会在热代码重新加载开始之前自动显示在应用程序中。
It's a really nice way to immediately determine if the app has already been compiled and you can test a part of it you're working on.
这是一种非常好的方法,可以立即确定该应用程序是否已被编译,并且您可以测试正在处理的应用程序的一部分。
Next.js is based on React, so one very useful tool we absolutely need to install (if you haven't already) is the React Developer Tools.
Next.js基于React,因此我们绝对需要安装(如果您尚未安装)一个非常有用的工具是React Developer Tools。
Available for both Chrome and Firefox, the React Developer Tools are an essential instrument you can use to inspect a React application.
React Developer Tools是可用于Chrome和Firefox的必备工具,可用于检查React应用程序。
Now, the React Developer Tools are not specific to Next.js but I want to introduce them because you might not be 100% familiar with all the tools React provides. It's best to go a little into debugging tooling than assuming you already know them.
现在,React开发人员工具并不特定于Next.js,但我想介绍它们,因为您可能不是100%熟悉React提供的所有工具。 最好花一点时间来调试工具,而不要假设您已经了解它们。
They provide an inspector that reveals the React components tree that builds your page, and for each component you can go and check the props, the state, hooks, and lots more.
他们提供了一个检查器,该检查器揭示了构建页面的React组件树,对于每个组件,您都可以检查道具,状态,钩子等等。
Once you have installed the React Developer Tools, you can open the regular browser devtools (in Chrome, it's right-click in the page, then click Inspect
) and you'll find 2 new panels: Components and Profiler.
一旦安装了React Developer Tools,就可以打开常规的浏览器devtools(在Chrome中,右键单击页面,然后单击Inspect
),您会发现2个新面板: Components和Profiler 。
If you move the mouse over the components, you'll see that in the page, the browser will select the parts that are rendered by that component.
如果将鼠标移到组件上,则会在页面中看到,浏览器将选择该组件渲染的部分。
If you select any component in the tree, the right panel will show you a reference to the parent component, and the props passed to it:
如果您在树中选择任何组件,则右侧面板将显示对父组件的引用以及传递给它的道具:
You can easily navigate by clicking around the component names.
您可以通过单击组件名称轻松浏览。
You can click the eye icon in the Developer Tools toolbar to inspect the DOM element, and also if you use the first icon, the one with the mouse icon (which conveniently sits under the similar regular DevTools icon), you can hover an element in the browser UI to directly select the React component that renders it.
您可以单击“开发人员工具”工具栏中的眼睛图标来检查DOM元素,如果您使用的是第一个图标(带有鼠标图标的图标,该图标通常位于类似的常规DevTools图标下),则可以将元素悬停在浏览器用户界面以直接选择呈现它的React组件。
You can use the bug
icon to log a component data to the console.
您可以使用bug
图标将组件数据记录到控制台。
This is pretty awesome because once you have the data printed there, you can right-click any element and press "Store as a global variable". For example here I did it with the url
prop, and I was able to inspect it in the console using the temporary variable assigned to it, temp1
:
这非常棒,因为一旦在其中打印了数据,就可以右键单击任何元素,然后按“存储为全局变量”。 例如,在这里我使用url
prop做到了这一点,并且我可以使用分配给它的临时变量temp1
在控制台中对其进行检查:
Using Source Maps, which are loaded by Next.js automatically in development mode, from the Components panel we can click the <>
code and the DevTools will switch to the Source panel, showing us the component source code:
使用由Next.js在开发模式下自动加载的Source Maps ,从Components面板中单击<>
代码,DevTools将切换到Source面板,向我们显示组件源代码:
The Profiler tab is even more awesome, if possible. It allows us to record an interaction in the app, and see what happens. I cannot show an example yet, because it needs at least 2 components to create an interaction, and we have just one now. I'll talk about this later.
如果可能的话,“ 探查器”选项卡更加出色。 它使我们能够在应用程序中记录交互 ,并观察发生了什么。 我还不能显示一个示例,因为它至少需要2个组件才能创建交互,而现在只有一个。 稍后再说。
I showed all screenshots using Chrome, but the React Developer Tools works in the same way in Firefox:
我使用Chrome显示了所有屏幕截图,但是React Developer Tools在Firefox中的工作方式相同:
In addition to the React Developer Tools, which are essential to building a Next.js application, I want to emphasize 2 ways to debug Next.js apps.
除了对构建Next.js应用程序必不可少的React Developer Tools之外,我还要强调两种调试Next.js应用程序的方法。
The first is obviously console.log()
and all the other Console API tools. The way Next apps work will make a log statement work in the browser console OR in the terminal where you started Next using npm run dev
.
首先显然是console.log()
和所有其他控制台API工具。 Next应用程序的工作方式将使日志语句在浏览器控制台中或在您使用npm run dev
启动Next的终端中npm run dev
。
In particular, if the page loads from the server, when you point the URL to it, or you hit the refresh button / cmd/ctrl-R, any console logging happens in the terminal.
特别是,如果页面是从服务器加载的,则将URL指向服务器时,或者单击刷新按钮/ cmd / ctrl-R,则任何控制台日志记录都会在终端中发生。
Subsequent page transitions that happen by clicking the mouse will make all console logging happen inside the browser.
通过单击鼠标进行的后续页面转换将使所有控制台记录都发生在浏览器内部。
Just remember if you are surprised by missing logging.
请记住,如果您对丢失日志感到惊讶。
Another tool that is essential is the debugger
statement. Adding this statement to a component will pause the browser rendering the page:
另一个必不可少的工具是debugger
语句。 将此语句添加到组件将使浏览器暂停呈现页面:
Really awesome because now you can use the browser debugger to inspect values and run your app one line at a time.
真棒,因为现在您可以使用浏览器调试器检查值并一次一行运行您的应用程序。
You can also use the VS Code debugger to debug server-side code. I mention this technique and this tutorial to set this up.
您还可以使用VS Code调试器来调试服务器端代码。 我提到了这项技术和本教程来进行设置。
Now that we have a good grasp of the tools we can use to help us develop Next.js apps, let's continue from where we left our first app:
既然我们已经掌握了可用于帮助开发Next.js应用程序的工具,那么让我们从剩下的第一个应用程序继续:
I want to add a second page to this website, a blog. It's going to be served into /blog
, and for the time being it will just contain a simple static page, just like our first index.js
component:
我想在此网站上添加第二页,即博客。 它将被提供给/blog
,并且暂时将只包含一个简单的静态页面,就像我们的第一个index.js
组件一样:
After saving the new file, the npm run dev
process already running is already capable of rendering the page, without the need to restart it.
保存新文件后,已经运行的npm run dev
进程已经能够呈现页面,而无需重新启动它。
When we hit the URL http://localhost:3000/blog we have the new page:
当我们访问URL http:// localhost:3000 / blog时,我们将打开新页面:
and here's what the terminal told us:
这是终端告诉我们的内容:
Now the fact that the URL is /blog
depends on just the filename, and its position under the pages
folder.
现在,URL为/blog
的事实仅取决于文件名及其在pages
文件夹下的位置。
You can create a pages/hey/ho
page, and that page will show up on the URL http://localhost:3000/hey/ho.
您可以创建一个pages/hey/ho
页面,该页面将显示在URL http:// localhost:3000 / hey / ho上 。
What does not matter, for the URL purposes, is the component name inside the file.
对于URL而言,无所谓的是文件内的组件名称。
Try going and viewing the source of the page, when loaded from the server it will list /_next/static/development/pages/blog.js
as one of the bundles loaded, and not /_next/static/development/pages/index.js
like in the home page. This is because thanks to automatic code splitting we don't need the bundle that serves the home page. Just the bundle that serves the blog page.
尝试浏览页面的源代码,当从服务器加载页面时,它会将/_next/static/development/pages/blog.js
列为已加载的捆绑包之一,而不是/_next/static/development/pages/index.js
就像在主页上一样。 这是因为通过自动代码拆分,我们不需要用于主页的捆绑软件。 只是用于博客页面的捆绑软件。
We can also just export an anonymous function from blog.js
:
我们也可以从blog.js
导出一个匿名函数:
export default () => (
Blog
)
or if you prefer the non-arrow function syntax:
或者,如果您更喜欢非箭头函数语法:
export default function() {
return (
Blog
)
}
Now that we have 2 pages, defined by index.js
and blog.js
, we can introduce links.
现在我们有2个页面,分别由index.js
和blog.js
定义,我们可以介绍链接。
Normal HTML links within pages are done using the a
tag:
页面内的普通HTML链接是使用a
标签完成的:
Blog
We can't do do that in Next.js.
我们无法在Next.js中做到这一点。
Why? We technically can, of course, because this is the Web and on the Web things never break (that's why we can still use the tag. But one of the main benefits of using Next is that once a page is loaded, transitions to other page are very fast thanks to client-side rendering.
为什么? 我们在技术上当然可以 ,因为这是Web 和网络的事情从来没有突破 (这就是为什么我们仍然可以使用标记,但使用接下来的主要好处之一是,一旦页面加载,转换借助客户端渲染,到其他页面的速度非常快。
If you use a plain a
link:
如果使用普通的a
链接:
const Index = () => (
Home page
Blog
)
export default Index
Now open the DevTools, and the Network panel in particular. The first time we load http://localhost:3000/
we get all the page bundles loaded:
现在打开DevTools ,尤其是“ 网络”面板 。 第一次加载http://localhost:3000/
我们将加载所有页面捆绑:
Now if you click the "Preserve log" button (to avoid clearing the Network panel), and click the "Blog" link, this is what happens:
现在,如果您单击“保留日志”按钮(以避免清除“网络”面板),然后单击“博客”链接,将发生以下情况:
We got all that JavaScript from the server, again! But.. we don't need all that JavaScript if we already got it. We'd just need the blog.js
page bundle, the only one that's new to the page.
我们再次从服务器获得了所有JavaScript! 但是..如果我们已经有了JavaScript,就不需要所有JavaScript。 我们只需要blog.js
页面捆绑包,这是blog.js
面上唯一的新捆绑包。
To fix this problem, we use a component provided by Next, called Link.
要解决此问题,我们使用Next提供的名为Link的组件。
We import it:
我们导入它:
import Link from 'next/link'
and then we use it to wrap our link, like this:
然后我们用它来包装我们的链接,像这样:
import Link from 'next/link'
const Index = () => (
Home page
Blog
)
export default Index
Now if you retry the thing we did previously, you'll be able to see that only the blog.js
bundle is loaded when we move to the blog page:
现在,如果您重试我们之前做过的事情,您将能够看到在移至博客页面时仅加载了blog.js
捆绑包:
and the page loaded so faster than before, the browser usual spinner on the tab didn't even appear. Yet the URL changed, as you can see. This is working seamlessly with the browser History API.
并且页面加载速度比以前快,该标签上的浏览器常规微调框甚至都没有出现。 如您所见,URL已更改。 这与浏览器的History API无缝配合。
This is client-side rendering in action.
这是实际的客户端渲染。
What if you now press the back button? Nothing is being loaded, because the browser still has the old index.js
bundle in place, ready to load the /index
route. It's all automatic!
如果现在按下返回按钮怎么办? 什么都没有加载,因为浏览器仍然具有旧的index.js
包,可以加载/index
路由。 都是自动的!
In the previous chapter we saw how to link the home to the blog page.
在上一章中,我们看到了如何将主页链接到博客页面。
A blog is a great use case for Next.js, one we'll continue to explore in this chapter by adding blog posts.
对于Next.js,博客是一个很好的用例,我们将在本章中通过添加博客文章继续进行探讨。
Blog posts have a dynamic URL. For example a post titled "Hello World" might have the URL /blog/hello-world
. A post titled "My second post" might have the URL /blog/my-second-post
.
博客文章具有动态URL。 例如,标题为“ Hello World”的帖子可能具有URL /blog/hello-world
。 标题为“我的第二条帖子”的帖子可能具有URL /blog/my-second-post
。
This content is dynamic, and might be taken from a database, markdown files or more.
此内容是动态的,可能取自数据库,降价文件或更多内容。
Next.js can serve dynamic content based on a dynamic URL.
Next.js可以基于动态URL提供动态内容。
We create a dynamic URL by creating a dynamic page with the []
syntax.
我们通过使用[]
语法创建动态页面来创建动态URL。
How? We add a pages/blog/[id].js
file. This file will handle all the dynamic URLs under the /blog/
route, like the ones we mentioned above: /blog/hello-world
, /blog/my-second-post
and more.
怎么样? 我们添加一个pages/blog/[id].js
文件。 该文件将处理/blog/
路由下的所有动态URL,就像我们上面提到的那样: /blog/hello-world
, /blog/my-second-post
等。
In the file name, [id]
inside the square brackets means that anything that's dynamic will be put inside the id
parameter of the query property of the router.
在文件名中,方括号内的[id]
表示动态的任何内容都将放置在router的query属性的id
参数内。
Ok, that's a bit too many things at once.
好的,这一次太多了。
What's the router?
什么是路由器 ?
The router is a library provided by Next.js.
路由器是Next.js提供的库。
We import it from next/router
:
我们从next/router
导入它:
import { useRouter } from 'next/router'
and once we have useRouter
, we instantiate the router object using:
一旦有了useRouter
,我们将使用以下方法实例化路由器对象:
const router = useRouter()
Once we have this router object, we can extract information from it.
一旦有了该路由器对象,就可以从中提取信息。
In particular we can get the dynamic part of the URL in the [id].js
file by accessing router.query.id
.
特别是,我们可以通过访问router.query.id
来获得[id].js
文件中URL的动态部分。
The dynamic part can also just be a portion of the URL, like post-[id].js
.
动态部分也可以只是URL的一部分,例如post-[id].js
。
So let's go on and apply all those things in practice.
因此,让我们继续实践所有这些内容。
Create the file pages/blog/[id].js
:
创建文件pages/blog/[id].js
:
import { useRouter } from 'next/router'
export default () => {
const router = useRouter()
return (
<>
Blog post
Post id: {router.query.id}
>
)
}
Now if you go to the http://localhost:3000/blog/test
router, you should see this:
现在,如果您转到http://localhost:3000/blog/test
路由器,则应该看到以下内容:
We can use this id
parameter to gather the post from a list of posts. From a database, for example. To keep things simple we'll add a posts.json
file in the project root folder:
我们可以使用此id
参数从帖子列表中收集帖子。 例如,来自数据库。 为了简单posts.json
我们将在项目根文件夹中添加一个posts.json
文件:
{
"test": {
"title": "test post",
"content": "Hey some post content"
},
"second": {
"title": "second post",
"content": "Hey this is the second post content"
}
}
Now we can import it and lookup the post from the id
key:
现在我们可以导入它并从id
键中查找帖子:
import { useRouter } from 'next/router'
import posts from '../../posts.json'
export default () => {
const router = useRouter()
const post = posts[router.query.id]
return (
<>
{post.title}
{post.content}
>
)
}
Reloading the page should show us this result:
重新加载页面应向我们显示以下结果:
But it's not! Instead, we get an error in the console, and an error in the browser, too:
但这不是! 相反,我们在控制台中出现错误,在浏览器中也出现错误:
Why? Because.. during rendering, when the component is initialized, the data is not there yet. We'll see how to provide the data to the component with getInitialProps in the next lesson.
为什么? 因为在渲染过程中初始化组件时,数据还不存在。 在下一课中,我们将了解如何使用getInitialProps将数据提供给组件。
For now, add a little if (!post) return
check before returning the JSX:
现在,在返回JSX之前添加一点if (!post) return
检查:
import { useRouter } from 'next/router'
import posts from '../../posts.json'
export default () => {
const router = useRouter()
const post = posts[router.query.id]
if (!post) return
return (
<>
{post.title}
{post.content}
>
)
}
Now things should work. Initially the component is rendered without the dynamic router.query.id
information. After rendering, Next.js triggers an update with the query value and the page displays the correct information.
现在一切正常。 最初,组件呈现时没有动态router.query.id
信息。 呈现后,Next.js会使用查询值触发更新,并且页面显示正确的信息。
And if you view source, there is that empty tag in the HTML:
而且,如果您查看源代码,则HTML中有一个空的标记:
We'll soon fix this issue that fails to implement SSR and this harms both loading times for our users, SEO and social sharing as we already discussed.
我们将尽快解决无法实施SSR的问题,这将损害我们的用户,SEO和社交共享的加载时间,正如我们已经讨论的那样。
We can complete the blog example by listing those posts in pages/blog.js
:
我们可以通过在pages/blog.js
列出这些帖子来完成博客示例:
import posts from '../posts.json'
const Blog = () => (
Blog
{Object.entries(posts).map((value, index) => {
return - {value[1].title}
})}
)
export default Blog
And we can link them to the individual post pages, by importing Link
from next/link
and using it inside the posts loop:
通过从next/link
导入Link
并在posts循环中使用它,我们可以将它们链接到各个帖子页面:
import Link from 'next/link'
import posts from '../posts.json'
const Blog = () => (
Blog
{Object.entries(posts).map((value, index) => {
return (
-
{value[1].title}
)
})}
)
export default Blog
I mentioned previously how the Link
Next.js component can be used to create links between 2 pages, and when you use it, Next.js transparently handles frontend routing for us, so when a user clicks a link, frontend takes care of showing the new page without triggering a new client/server request and response cycle, as it normally happens with web pages.
我之前曾提到过如何使用Link
Next.js组件在两个页面之间创建链接,并且在使用它时,Next.js 透明地为我们处理前端路由 ,因此当用户单击链接时,前端会负责显示网页,而不会触发新的客户端/服务器请求和响应周期。
There's another thing that Next.js does for you when you use Link
.
当您使用Link
时,Next.js会为您做另一件事。
As soon as an element wrapped within appears in the viewport (which means it's visible to the website user), Next.js prefetches the URL it points to, as long as it's a local link (on your website), making the application super fast to the viewer.
只要包装在中的元素出现在视口中(这意味着它对网站用户可见),Next.js就会预取它指向的URL,只要它是本地链接(在您的网站上),就可以对观看者来说应用程序超级快。
This behavior is only being triggered in production mode (we'll talk about this in-depth later), which means you have to stop the application if you are running it with npm run dev
, compile your production bundle with npm run build
and run it with npm run start
instead.
此行为仅在生产模式下触发(我们将在以后进行深入讨论),这意味着如果使用npm run dev
运行该应用程序,则必须停止该应用程序;使用npm run build
编译生产包,然后运行用npm run start
代替。
Using the Network inspector in the DevTools you'll notice that any links above the fold, at page load, start the prefetching as soon as the load
event has been fired on your page (triggered when the page is fully loaded, and happens after the DOMContentLoaded
event).
使用DevTools中的网络检查器,您会注意到页面加载时折叠上方的所有链接在页面上触发load
事件后立即开始预提取(在页面完全加载时触发,并在DOMContentLoaded
事件)。
Any other Link
tag not in the viewport will be prefetched when the user scrolls and it
用户滚动浏览时,视口中未包含的任何其他Link
标签将被预取
Prefetching is automatic on high speed connections (Wifi and 3g+ connections, unless the browser sends the Save-Data
HTTP Header.
除非浏览器发送Save-Data
HTTP Header ,否则高速连接(Wifi和3g +连接)上的自动预取是自动的。
You can opt out from prefetching individual Link
instances by setting the prefetch
prop to false
:
您可以通过将prefetch
prop设置为false
来退出预取各个Link
实例:
A link
One very important feature when working with links is determining what is the current URL, and in particular assigning a class to the active link, so we can style it differently from the other ones.
使用链接时,一个非常重要的功能是确定当前URL是什么,尤其是为活动链接分配一个类,因此我们可以将其样式设置为与其他URL不同。
This is especially useful in your site header, for example.
例如,这在您的网站标题中特别有用。
The Next.js default Link
component offered in next/link
does not do this automatically for us.
next/link
中提供的Next.js默认Link
组件不会自动为我们执行此操作。
We can create a Link component ourselves, and we store it in a file Link.js
in the Components folder, and import that instead of the default next/link
.
我们可以自己创建一个Link组件,然后将其存储在Components文件夹中的Link.js
文件中,然后导入它而不是默认的next/link
。
In this component, we'll first import React from react
, Link from next/link
and the useRouter
hook from next/router
.
在此组件中,我们将首先从react
导入React,从next/link
导入Link,从next/router
useRouter
钩子。
Inside the component we determine if the current path name matches the href
prop of the component, and if so we append the selected
class to the children.
在组件内部,我们确定当前路径名是否与组件的href
属性匹配,如果是,则将selected
类附加到子代。
We finally return this children with the updated class, using React.cloneElement()
:
最后,我们使用React.cloneElement()
返回带有更新类的子级:
import React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
export default ({ href, children }) => {
const router = useRouter()
let className = children.props.className || ''
if (router.pathname === href) {
className = `${className} selected`
}
return {React.cloneElement(children, { className })}
}
next/router
(Using next/router
)We already saw how to use the Link component to declaratively handle routing in Next.js apps.
我们已经了解了如何使用Link组件在Next.js应用程序中声明性地处理路由。
It's really handy to manage routing in JSX, but sometimes you need to trigger a routing change programmatically.
在JSX中管理路由确实非常方便,但是有时您需要以编程方式触发路由更改。
In this case, you can access the Next.js Router directly, provided in the next/router
package, and call its push()
method.
在这种情况下,您可以直接访问next/router
包中提供的Next.js路由器,并调用其push()
方法。
Here's an example of accessing the router:
这是访问路由器的示例:
import { useRouter } from 'next/router'
export default () => {
const router = useRouter()
//...
}
Once we get the router object by invoking useRouter()
, we can use its methods.
通过调用useRouter()
获得路由器对象后,就可以使用其方法了。
This is the client side router, so methods should only be used in frontend facing code. The easiest way to ensure this is to wrap calls in the useEffect()
React hook, or inside componentDidMount()
in React stateful components.
这是客户端路由器,因此方法仅应在面向前端的代码中使用。 确保这一点的最简单方法是将调用包装在React的useEffect()
,或包装在React有状态组件的componentDidMount()
中。
The ones you'll likely use the most are push()
and prefetch()
.
您可能最常使用的是push()
和prefetch()
。
push()
allows us to programmatically trigger a URL change, in the frontend:
push()
允许我们在前端以编程方式触发URL更改:
router.push('/login')
prefetch()
allows us to programmatically prefetch a URL, useful when we don't have a Link
tag which automatically handles prefetching for us:
prefetch()
允许我们以编程方式预取URL,这在我们没有可自动为我们处理预取的Link
标记时非常有用:
router.prefetch('/login')
Full example:
完整示例:
import { useRouter } from 'next/router'
export default () => {
const router = useRouter()
useEffect(() => {
router.prefetch('/login')
})
}
You can also use the router to listen for route change events.
您也可以使用路由器侦听路由更改事件 。
In the previous chapter we had an issue with dynamically generating the post page, because the component required some data up front, and when we tried to get the data from the JSON file:
在上一章中,动态生成帖子页面存在一个问题,因为该组件需要预先提供一些数据,并且当我们尝试从JSON文件中获取数据时:
import { useRouter } from 'next/router'
import posts from '../../posts.json'
export default () => {
const router = useRouter()
const post = posts[router.query.id]
return (
<>
{post.title}
{post.content}
>
)
}
we got this error:
我们收到此错误:
How do we solve this? And how do we make SSR work for dynamic routes?
我们该如何解决呢? 以及如何使SSR用于动态路由?
We must provide the component with props, using a special function called getInitialProps()
which is attached to the component.
我们必须使用附加到组件的特殊函数getInitialProps()
为组件提供道具。
To do so, first we name the component:
为此,首先我们将组件命名为:
const Post = () => {
//...
}
export default Post
then we add the function to it:
然后我们添加功能:
const Post = () => {
//...
}
Post.getInitialProps = () => {
//...
}
export default Post
This function gets an object as its argument, which contains several properties. In particular, the thing we are interested into now is that we get the query
object, the one we used previously to get the post id.
此函数将一个对象作为其参数,其中包含多个属性。 特别是,我们现在感兴趣的是获取query
对象,这是我们先前用于获取帖子ID的对象。
So we can get it using the object destructuring syntax:
因此,我们可以使用对象分解语法来获取它:
Post.getInitialProps = ({ query }) => {
//...
}
Now we can return the post from this function:
现在我们可以从该函数返回帖子:
Post.getInitialProps = ({ query }) => {
return {
post: posts[query.id]
}
}
And we can also remove the import of useRouter
, and we get the post from the props
property passed to the Post
component:
我们还可以删除useRouter
的导入,然后从props
属性中获取该帖子,并将其传递给Post
组件:
import posts from '../../posts.json'
const Post = props => {
return (
{props.post.title}
{props.post.content}
)
}
Post.getInitialProps = ({ query }) => {
return {
post: posts[query.id]
}
}
export default Post
Now there will be no error, and SSR will be working as expected, as you can see checking view source:
现在将没有任何错误,并且SSR将按预期工作,如您所见,检查视图源:
The getInitialProps
function will be executed on the server side, but also on the client side, when we navigate to a new page using the Link
component as we did.
当我们像以前一样使用Link
组件导航到新页面时, getInitialProps
函数将在服务器端和客户端执行。
It's important to note that getInitialProps
gets, in the context object it receives, in addition to the query
object these other properties:
重要的是要注意,除了query
对象外, getInitialProps
还从接收的上下文对象中获取以下其他属性:
pathname
: the path
section of URL
pathname
:URL的path
部分
asPath
- String of the actual path (including the query) shows in the browser
asPath
浏览器中显示的实际路径(包括查询)的字符串
which in the case of calling http://localhost:3000/blog/test
will respectively result to:
在调用http://localhost:3000/blog/test
将分别导致:
/blog/[id]
/blog/[id]
/blog/test
/blog/test
And in the case of server side rendering, it will also receive:
对于服务器端渲染,它还将收到:
req
: the HTTP request object
req
:HTTP请求对象
res
: the HTTP response object
res
:HTTP响应对象
err
: an error object
err
:错误对象
req
and res
will be familiar to you if you've done any Node.js coding.
如果您已完成任何Node.js编码,那么req
和res
将会为您所熟悉。
How do we style React components in Next.js?
我们如何在Next.js中设置React组件的样式?
We have a lot of freedom, because we can use whatever library we prefer.
我们有很多自由,因为我们可以使用我们喜欢的任何库。
But Next.js comes with styled-jsx
built-in, because that's a library built by the same people working on Next.js.
但是Next.js内置了styled-jsx
,因为那是由从事Next.js的同一个人构建的库。
And it's a pretty cool library that provides us scoped CSS, which is great for maintainability because the CSS is only affecting the component it's applied to.
它是一个非常酷的库,为我们提供了范围内CSS,这对于可维护性非常有用,因为CSS仅会影响所应用的组件。
I think this is a great approach at writing CSS, without the need to apply additional libraries or preprocessors that add complexity.
我认为这是编写CSS的好方法,无需应用其他会增加复杂性的库或预处理器。
To add CSS to a React component in Next.js we insert it inside a snippet in the JSX, which start with
要将CSS添加到Next.js中的React组件中,我们将其插入JSX的一个片段中,该片段以
Inside this weird blocks we write plain CSS, as we'd do in a .css
file:
在这个怪异的代码块中,我们编写了纯CSS,就像在.css
文件中所做的那样:
You write it inside the JSX, like this:
您可以在JSX内编写它,如下所示:
const Index = () => (
Home page
)
export default Index
Inside the block we can use interpolation to dynamically change the values. For example here we assume a size
prop is being passed by the parent component, and we use it in the styled-jsx
block:
在块内部,我们可以使用插值来动态更改值。 例如,在此我们假设父组件正在传递一个size
道具,并在styled-jsx
块中使用它:
const Index = props => (
Home page
)
If you want to apply some CSS globally, not scoped to a component, you add the global
keyword to the style
tag:
如果要全局应用某些CSS,而不是将其应用于组件,则将global
关键字添加到style
标签:
If you want to import an external CSS file in a Next.js component, you have to first install @zeit/next-css
:
如果要在Next.js组件中导入外部CSS文件,则必须首先安装@zeit/next-css
:
npm install @zeit/next-css
and then create a configuration file in the root of the project, called next.config.js
, with this content:
然后在项目的根目录中创建一个配置文件,名为next.config.js
,其内容如下:
const withCSS = require('@zeit/next-css')
module.exports = withCSS()
After restarting the Next app, you can now import CSS like you normally do with JavaScript libraries or components:
重新启动Next应用程序后,您现在可以像通常使用JavaScript库或组件一样导入CSS:
import '../style.css'
You can also import a SASS file directly, using the @zeit/next-sass
library instead.
您也可以直接使用@zeit/next-sass
库导入SASS文件。
From any Next.js page component, you can add information to the page header.
您可以从任何Next.js页面组件向页面标题添加信息。
This is handy when:
在以下情况下方便使用:
How can you do so?
你该怎么做?
Inside every component you can import the Head
component from next/head
and include it in your component JSX output:
在每个组件内部,您可以从next/head
导入Head
组件,并将其包含在组件JSX输出中:
import Head from 'next/head'
const House = props => (
The page title
{/* the rest of the JSX */}
)
export default House
You can add any HTML tag you'd like to appear in the section of the page.
您可以添加任何想要显示在页面部分HTML标记。
When mounting the component, Next.js will make sure the tags inside Head
are added to the heading of the page. Same when unmounting the component, Next.js will take care of removing those tags.
在安装组件时,Next.js将确保Head
内的标签已添加到页面标题。 与卸载组件相同,Next.js将负责删除这些标记。
All the pages on your site look more or less the same. There's a chrome window, a common base layer, and you just want to change what's inside.
您网站上的所有页面看起来大致相同。 有一个chrome窗口,一个公共基础层,您只想更改其中的内容。
There's a nav bar, a sidebar, and then the actual content.
有一个导航栏,一个边栏,然后是实际内容。
How do you build such system in Next.js?
您如何在Next.js中构建这样的系统?
There are 2 ways. One is using a Higher Order Component, by creating a components/Layout.js
component:
有两种方法。 一种是通过创建components/Layout.js
组件来使用高阶组件 :
export default Page => {
return () => (
)
}
In there we can import separate components for heading and/or sidebar, and we can also add all the CSS we need.
在这里,我们可以为标题和/或侧边栏导入单独的组件,还可以添加所需的所有CSS。
And you use it in every page like this:
您可以在每个页面中使用它,如下所示:
import withLayout from '../components/Layout.js'
const Page = () => Here's a page!
export default withLayout(Page)
But I found this works only for simple cases, where you don't need to call getInitialProps()
on a page.
但是我发现这仅适用于简单的情况,您无需在页面上调用getInitialProps()
。
Why?
为什么?
Because getInitialProps()
gets only called on the page component. But if we export the Higher Order Component withLayout() from a page, Page.getInitialProps()
is not called. withLayout.getInitialProps()
would.
因为getInitialProps()
仅在页面组件上被调用。 但是,如果我们从页面导出withLayout()的高阶组件,则不会调用Page.getInitialProps()
。 withLayout.getInitialProps()
会。
To avoid unnecessarily complicating our codebase, the alternative approach is to use props:
To avoid unnecessarily complicating our codebase, the alternative approach is to use props:
export default props => (
)
and in our pages now we use it like this:
and in our pages now we use it like this:
import Layout from '../components/Layout.js'
const Page = () => (
Here's a page!
)} />
)
This approach lets us use getInitialProps()
from within our page component, with the only downside of having to write the component JSX inside the content
prop:
This approach lets us use getInitialProps()
from within our page component, with the only downside of having to write the component JSX inside the content
prop:
import Layout from '../components/Layout.js'
const Page = () => (
Here's a page!
)} />
)
Page.getInitialProps = ({ query }) => {
//...
}
In addition to creating page routes, which means pages are served to the browser as Web pages, Next.js can create API routes.
In addition to creating page routes , which means pages are served to the browser as Web pages, Next.js can create API routes .
This is a very interesting feature because it means that Next.js can be used to create a frontend for data that is stored and retrieved by Next.js itself, transferring JSON via fetch requests.
This is a very interesting feature because it means that Next.js can be used to create a frontend for data that is stored and retrieved by Next.js itself, transferring JSON via fetch requests.
API routes live under the /pages/api/
folder and are mapped to the /api
endpoint.
API routes live under the /pages/api/
folder and are mapped to the /api
endpoint.
This feature is very useful when creating applications.
This feature is very useful when creating applications.
In those routes, we write Node.js code (rather than React code). It's a paradigm shift, you move from the frontend to the backend, but very seamlessly.
In those routes, we write Node.js code (rather than React code). It's a paradigm shift, you move from the frontend to the backend, but very seamlessly.
Say you have a /pages/api/comments.js
file, whose goal is to return the comments of a blog post as JSON.
Say you have a /pages/api/comments.js
file, whose goal is to return the comments of a blog post as JSON.
Say you have a list of comments stored in a comments.json
file:
Say you have a list of comments stored in a comments.json
file:
[
{
"comment": "First"
},
{
"comment": "Nice post"
}
]
Here's a sample code, which returns to the client the list of comments:
Here's a sample code, which returns to the client the list of comments:
import comments from './comments.json'
export default (req, res) => {
res.status(200).json(comments)
}
It will listen on the /api/comments
URL for GET requests, and you can try calling it using your browser:
It will listen on the /api/comments
URL for GET requests, and you can try calling it using your browser:
API routes can also use dynamic routing like pages, use the []
syntax to create a dynamic API route, like /pages/api/comments/[id].js
which will retrieve the comments specific to a post id.
API routes can also use dynamic routing like pages, use the []
syntax to create a dynamic API route, like /pages/api/comments/[id].js
which will retrieve the comments specific to a post id.
Inside the [id].js
you can retrieve the id
value by looking it up inside the req.query
object:
Inside the [id].js
you can retrieve the id
value by looking it up inside the req.query
object:
import comments from '../comments.json'
export default (req, res) => {
res.status(200).json({ post: req.query.id, comments })
}
Heres you can see the above code in action:
Heres you can see the above code in action:
In dynamic pages, you'd need to import useRouter
from next/router
, then get the router object using const router = useRouter()
, and then we'd be able to get the id
value using router.query.id
.
In dynamic pages, you'd need to import useRouter
from next/router
, then get the router object using const router = useRouter()
, and then we'd be able to get the id
value using router.query.id
.
In the server-side it's all easier, as the query is attached to the request object.
In the server-side it's all easier, as the query is attached to the request object.
If you do a POST request, all works in the same way - it all goes through that default export.
If you do a POST request, all works in the same way - it all goes through that default export.
To separate POST from GET and other HTTP methods (PUT, DELETE), lookup the req.method
value:
To separate POST from GET and other HTTP methods (PUT, DELETE), lookup the req.method
value:
export default (req, res) => {
switch (req.method) {
case 'GET':
//...
break
case 'POST':
//...
break
default:
res.status(405).end() //Method Not Allowed
break
}
}
In addition to req.query
and req.method
we already saw, we have access to cookies by referencing req.cookies
, the request body in req.body
.
In addition to req.query
and req.method
we already saw, we have access to cookies by referencing req.cookies
, the request body in req.body
.
Under the hoods, this is all powered by Micro, a library that powers asynchronous HTTP microservices, made by the same team that built Next.js.
Under the hoods, this is all powered by Micro , a library that powers asynchronous HTTP microservices, made by the same team that built Next.js.
You can make use of any Micro middleware in our API routes to add more functionality.
You can make use of any Micro middleware in our API routes to add more functionality.
In your page components, you can execute code only in the server-side or on the client-side, by checking the window
property.
In your page components, you can execute code only in the server-side or on the client-side, by checking the window
property.
This property is only existing inside the browser, so you can check
This property is only existing inside the browser, so you can check
if (typeof window === 'undefined') {
}
and add the server-side code in that block.
and add the server-side code in that block.
Similarly, you can execute client-side code only by checking
Similarly, you can execute client-side code only by checking
if (typeof window !== 'undefined') {
}
JS Tip: We use the typeof
operator here because we can't detect a value to be undefined in other ways. We can't do if (window === undefined)
because we'd get a "window is not defined" runtime error
JS Tip: We use the typeof
operator here because we can't detect a value to be undefined in other ways. We can't do if (window === undefined)
because we'd get a "window is not defined" runtime error
Next.js, as a build-time optimization, also removes the code that uses those checks from bundles. A client-side bundle will not include the content wrapped into a if (typeof window === 'undefined') {}
block.
Next.js, as a build-time optimization, also removes the code that uses those checks from bundles. A client-side bundle will not include the content wrapped into a if (typeof window === 'undefined') {}
block.
Deploying an app is always left last in tutorials.
Deploying an app is always left last in tutorials.
Here I want to introduce it early, just because it's so easy to deploy a Next.js app that we can dive into it now, and then move on to other more complex topics later on.
Here I want to introduce it early, just because it's so easy to deploy a Next.js app that we can dive into it now, and then move on to other more complex topics later on.
Remember in the "How to install Next.js" chapter I told you to add those 3 lines to the package.json
script
section:
Remember in the "How to install Next.js" chapter I told you to add those 3 lines to the package.json
script
section:
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
We used npm run dev
up to now, to call the next
command installed locally in node_modules/next/dist/bin/next
. This started the development server, which provided us source maps and hot code reloading, two very useful features while debugging.
We used npm run dev
up to now, to call the next
command installed locally in node_modules/next/dist/bin/next
. This started the development server, which provided us source maps and hot code reloading , two very useful features while debugging.
The same command can be invoked to build the website passing the build
flag, by running npm run build
. Then, the same command can be used to start the production app passing the start
flag, by running npm run start
.
The same command can be invoked to build the website passing the build
flag, by running npm run build
. Then, the same command can be used to start the production app passing the start
flag, by running npm run start
.
Those 2 commands are the ones we must invoke to successfully deploy the production version of our site locally. The production version is highly optimized and does not come with source maps and other things like hot code reloading that would not be beneficial to our end users.
Those 2 commands are the ones we must invoke to successfully deploy the production version of our site locally. The production version is highly optimized and does not come with source maps and other things like hot code reloading that would not be beneficial to our end users.
So, let's create a production deploy of our app. Build it using:
So, let's create a production deploy of our app. Build it using:
npm run build
The output of the command tells us that some routes (/
and /blog
are now prerendered as static HTML, while /blog/[id]
will be served by the Node.js backend.
The output of the command tells us that some routes ( /
and /blog
are now prerendered as static HTML, while /blog/[id]
will be served by the Node.js backend.
Then you can run npm run start
to start the production server locally:
Then you can run npm run start
to start the production server locally:
npm run start
Visiting http://localhost:3000 will show us the production version of the app, locally.
Visiting http://localhost:3000 will show us the production version of the app, locally.
In the previous chapter we deployed the Next.js application locally.
In the previous chapter we deployed the Next.js application locally.
How do we deploy it to a real web server, so other people can access it?
How do we deploy it to a real web server, so other people can access it?
One of the most simple ways to deploy a Next application is through the Now platform created by Zeit, the same company that created the Open Source project Next.js. You can use Now to deploy Node.js apps, Static Websites, and much more.
One of the most simple ways to deploy a Next application is through the Now platform created by Zeit , the same company that created the Open Source project Next.js. You can use Now to deploy Node.js apps, Static Websites, and much more.
Now makes the deployment and distribution step of an app very, very simple and fast, and in addition to Node.js apps, they also support deploying Go, PHP, Python and other languages.
Now makes the deployment and distribution step of an app very, very simple and fast, and in addition to Node.js apps, they also support deploying Go, PHP, Python and other languages.
You can think of it as the "cloud", as you don't really know where your app will be deployed, but you know that you will have a URL where you can reach it.
You can think of it as the "cloud", as you don't really know where your app will be deployed, but you know that you will have a URL where you can reach it.
Now is free to start using, with generous free plan that currently includes 100GB of hosting, 1000 serverless functions invocations per day, 1000 builds per month, 100GB of bandwidth per month, and one CDN location. The pricing page helps get an idea of the costs if you need more.
Now is free to start using, with generous free plan that currently includes 100GB of hosting, 1000 serverless functions invocations per day, 1000 builds per month, 100GB of bandwidth per month, and one CDN location. The pricing page helps get an idea of the costs if you need more.
The best way to start using Now is by using the official Now CLI:
The best way to start using Now is by using the official Now CLI:
npm install -g now
Once the command is available, run
Once the command is available, run
now login
and the app will ask you for your email.
and the app will ask you for your email.
If you haven't registered already, create an account on https://zeit.co/signup before continuing, then add your email to the CLI client.
If you haven't registered already, create an account on https://zeit.co/signup before continuing, then add your email to the CLI client.
Once this is done, from the Next.js project root folder run
Once this is done, from the Next.js project root folder run
now
and the app will be instantly deployed to the Now cloud, and you'll be given the unique app URL:
and the app will be instantly deployed to the Now cloud, and you'll be given the unique app URL:
Once you run the now
program, the app is deployed to a random URL under the now.sh
domain.
Once you run the now
program, the app is deployed to a random URL under the now.sh
domain.
We can see 3 different URLs in the output given in the image:
We can see 3 different URLs in the output given in the image:
https://firstproject-2pv7khwwr.now.sh
https://firstproject-2pv7khwwr.now.sh
https://firstproject-sepia-ten.now.sh
https://firstproject-sepia-ten.now.sh
https://firstproject.flaviocopes.now.sh
https://firstproject.flaviocopes.now.sh
Why so many?
为什么那么多?
The first is the URL identifying the deploy. Every time we deploy the app, this URL will change.
The first is the URL identifying the deploy. Every time we deploy the app, this URL will change.
You can test immediately by changing something in the project code, and running now
again:
You can test immediately by changing something in the project code, and running now
again:
The other 2 URLs will not change. The first is a random one, the second is your project name (which defaults to the current project folder, your account name and then now.sh
.
The other 2 URLs will not change. The first is a random one, the second is your project name (which defaults to the current project folder, your account name and then now.sh
.
If you visit the URL, you will see the app deployed to production.
If you visit the URL, you will see the app deployed to production.
You can configure Now to serve the site to your own custom domain or subdomain, but I will not dive into that right now.
You can configure Now to serve the site to your own custom domain or subdomain, but I will not dive into that right now.
The now.sh
subdomain is enough for our testing purposes.
The now.sh
subdomain is enough for our testing purposes.
Next provides us a way to analyze the code bundles that are generated.
Next provides us a way to analyze the code bundles that are generated.
Open the package.json file of the app and in the scripts section add those 3 new commands:
Open the package.json file of the app and in the scripts section add those 3 new commands:
"analyze": "cross-env ANALYZE=true next build",
"analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
"analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build"
Like this:
像这样:
{
"name": "firstproject",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"analyze": "cross-env ANALYZE=true next build",
"analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
"analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"next": "^9.1.2",
"react": "^16.11.0",
"react-dom": "^16.11.0"
}
}
then install those 2 packages:
then install those 2 packages:
npm install --dev cross-env @next/bundle-analyzer
Create a next.config.js
file in the project root, with this content:
Create a next.config.js
file in the project root, with this content:
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
module.exports = withBundleAnalyzer({})
Now run the command
Now run the command
npm run analyze
This should open 2 pages in the browser. One for the client bundles, and one for the server bundles:
This should open 2 pages in the browser. One for the client bundles, and one for the server bundles:
This is incredibly useful. You can inspect what's taking the most space in the bundles, and you can also use the sidebar to exclude bundles, for an easier visualization of the smaller ones:
This is incredibly useful. You can inspect what's taking the most space in the bundles, and you can also use the sidebar to exclude bundles, for an easier visualization of the smaller ones:
Being able to visually analyze a bundle is great because we can optimize our application very easily.
Being able to visually analyze a bundle is great because we can optimize our application very easily.
Say we need to load the Moment library in our blog posts. Run:
Say we need to load the Moment library in our blog posts. 跑:
npm install moment
to include it in the project.
to include it in the project.
Now let's simulate the fact we need it on two different routes: /blog
and /blog/[id]
.
Now let's simulate the fact we need it on two different routes: /blog
and /blog/[id]
.
We import it in pages/blog/[id].js
:
We import it in pages/blog/[id].js
:
import moment from 'moment'
...
const Post = props => {
return (
{props.post.title}
Published on {moment().format('dddd D MMMM YYYY')}
{props.post.content}
)
}
I'm just adding today's date, as an example.
I'm just adding today's date, as an example.
This will include Moment.js in the blog post page bundle, as you can see by running npm run analyze
:
This will include Moment.js in the blog post page bundle, as you can see by running npm run analyze
:
See that we now have a red entry in /blog/[id]
, the route that we added Moment.js to!
See that we now have a red entry in /blog/[id]
, the route that we added Moment.js to!
It went from ~1kB to 350kB, quite a big deal. And this is because the Moment.js library itself is 349kB.
It went from ~1kB to 350kB, quite a big deal. And this is because the Moment.js library itself is 349kB.
The client bundles visualization now shows us that the bigger bundle is the page one, which before was very little. And 99% of its code is Moment.js.
The client bundles visualization now shows us that the bigger bundle is the page one, which before was very little. And 99% of its code is Moment.js.
Every time we load a blog post we are going to have all this code transferred to the client. Which is not ideal.
Every time we load a blog post we are going to have all this code transferred to the client. Which is not ideal.
One fix would be to look for a library with a smaller size, as Moment.js is not known for being lightweight (especially out of the box with all the locales included), but let's assume for the sake of the example that we must use it.
One fix would be to look for a library with a smaller size, as Moment.js is not known for being lightweight (especially out of the box with all the locales included), but let's assume for the sake of the example that we must use it.
What we can do instead is separating all the Moment code in a separate bundle.
What we can do instead is separating all the Moment code in a separate bundle .
How? Instead of importing Moment at the component level, we perform an async import inside getInitialProps
, and we calculate the value to send to the component.Remember that we can't return complex objects inside the getInitialProps()
returned object, so we calculate the date inside it:
怎么样? Instead of importing Moment at the component level, we perform an async import inside getInitialProps
, and we calculate the value to send to the component.Remember that we can't return complex objects inside the getInitialProps()
returned object, so we calculate the date inside it:
import posts from '../../posts.json'
const Post = props => {
return (
{props.post.title}
Published on {props.date}
{props.post.content}
)
}
Post.getInitialProps = async ({ query }) => {
const moment = (await import('moment')).default()
return {
date: moment.format('dddd D MMMM YYYY'),
post: posts[query.id]
}
}
export default Post
See that special call to .default()
after await import
? It's needed to reference the default export in a dynamic import (see https://v8.dev/features/dynamic-import)
See that special call to .default()
after await import
? It's needed to reference the default export in a dynamic import (see https://v8.dev/features/dynamic-import )
Now if we run npm run analyze
again, we can see this:
Now if we run npm run analyze
again, we can see this:
Our /blog/[id]
bundle is again very small, as Moment has been moved to its own bundle file, loaded separately by the browser.
Our /blog/[id]
bundle is again very small, as Moment has been moved to its own bundle file, loaded separately by the browser.
There is a lot more to know about Next.js. I didn't talk about managing user sessions with login, serverless, managing databases, and so on.
There is a lot more to know about Next.js. I didn't talk about managing user sessions with login, serverless, managing databases, and so on.
The goal of this Handbook is not to teach you everything, but instead it aims to introduce you, gradually, to all the power of Next.js.
The goal of this Handbook is not to teach you everything, but instead it aims to introduce you, gradually, to all the power of Next.js.
The next step I recommend is to take a good read at the Next.js official documentation to find out more about all the features and functionality I didn't talk about, and take a look at all the additional functionalities introduced by Next.js plugins, some of which are pretty amazing.
The next step I recommend is to take a good read at the Next.js official documentation to find out more about all the features and functionality I didn't talk about, and take a look at all the additional functionalities introduced by Next.js plugins , some of which are pretty amazing.
You can reach me on Twitter @flaviocopes.
You can reach me on Twitter @flaviocopes .
Also check out my website, flaviocopes.com.
Also check out my website, flaviocopes.com .
Note: you can download a PDF / ePub / Mobi version of this tutorial so you can read it offline!
Note: you can download a PDF / ePub / Mobi version of this tutorial so you can read it offline !
翻译自: https://www.freecodecamp.org/news/the-next-js-handbook/
next.js