diff --git a/README-zh-CN.md b/README-zh-CN.md index a1f712583e..d617f60e8c 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -90,7 +90,7 @@ Next.js 是一个轻量级的 React 服务端渲染应用框架。 ### 安装 -安装它: +在项目文件夹中运行: ```bash npm install --save next react react-dom @@ -116,14 +116,13 @@ npm install --save next react react-dom export default () =>
Welcome to next.js!
; ``` -运行 `npm run dev` 命令并打开 `http://localhost:3000`。 如果你想使用其他端口,可运行 `npm run dev -- -p <设置端口号>`. +运行 `npm run dev` 命令并打开 `http://localhost:3000`。 要使用其他端口,你可以运行 `npm run dev -- -p `. -目前为止我们可以了解到: +到目前为止,我们做到: - 自动打包编译 (使用 webpack 和 babel) - 热加载 -- 以 `./pages`作为服务端的渲染和索引 -- Static file serving. `./static/` is mapped to `/static/` (given you [create a `./static/` directory](#static-file-serving-eg-images) inside your project) +- 以 `./pages`作为服务的渲染和索引 - 静态文件服务. `./static/` 映射到 `/static/` (可以 [创建一个静态目录](#static-file-serving-eg-images) 在你的项目中) 这里有个简单的案例,可以下载看看 [sample app - nextgram](https://github.com/zeit/nextgram) @@ -132,7 +131,7 @@ export default () =>
Welcome to next.js!
; ### 代码自动分割 -每个页面只会导入`import`中绑定以及被用到的代码. 也就是说并不会加载不需要的代码! +每个页面只会导入`import`中绑定以及被用到的代码. 这意味着页面不会加载不必要的代码 ```jsx import cowsay from "cowsay-browser"; @@ -182,7 +181,7 @@ export default () => ( ); ``` -想查看更多案例可以点击 [styled-jsx documentation](https://www.npmjs.com/package/styled-jsx)查看. +想查看更多案例可以点击 [styled-jsx documentation](https://www.npmjs.com/package/styled-jsx). @@ -201,7 +200,7 @@ export default () => ( export default () =>

hi there

; ``` -更复杂的内嵌样式解决方案,特别是服务端渲染的时样式更改。我们可以通过包裹自定义 Document,来添加样式,案例如下:[custom ``](#user-content-custom-document) +更复杂的内嵌样式解决方案,特别是服务端渲染时的样式更改。我们可以通过包裹自定义 Document,来添加样式,案例如下:[custom ``](#user-content-custom-document) @@ -224,7 +223,7 @@ export default () =>

hi there

; export default () => my image; ``` -\_注意:不要自定义静态文件夹的名字,只能叫`static` ,因为只有这个名字 Next.js 才会把它当作静态资源。 +_注意:不要自定义静态文件夹的名字,只能叫`static` ,因为只有这个名字 Next.js 才会把它当作静态资源。_ @@ -284,7 +283,7 @@ export default () => ( 只有第二个``才被渲染。 -注意:在卸载组件时,``的内容将被清除。请确保每个页面都在其``定义了所需要的内容,而不是假设其他页面已经加过了 +_注意:在卸载组件时,``的内容将被清除。请确保每个页面都在其``定义了所需要的内容,而不是假设其他页面已经加过了_ @@ -295,7 +294,7 @@ export default () => (

-如果你需要一个有状态、生命周期或有初始数据的 React 组件(而不是上面的无状态函数),如下所示: +当你需要状态,生命周期钩子或初始数据填充时,你可以导出`React.Component`(而不是上面的无状态函数),如下所示: ```jsx import React from "react"; @@ -312,15 +311,15 @@ export default class extends React.Component { } ``` -相信你注意到,当页面渲染时加载数据,我们使用了一个异步方法`getInitialProps`。它能异步获取 JS 普通对象,并绑定在`props`上 +请注意,当页面渲染时加载数据,我们使用了一个异步静态方法`getInitialProps`。它能异步获取 JS 普通对象,并绑定在`props`上。 当服务渲染时,`getInitialProps`将会把数据序列化,就像`JSON.stringify`。所以确保`getInitialProps`返回的是一个普通 JS 对象,而不是`Date`, `Map` 或 `Set`类型。 当页面初次加载时,`getInitialProps`只会在服务端执行一次。`getInitialProps`只有在路由切换的时候(如`Link`组件跳转或路由自定义跳转)时,客户端的才会被执行。 -当页面初始化加载时,`getInitialProps`只会加载在服务端。只有当路由跳转(`Link`组件跳转或 API 方法跳转)时,客户端才会执行`getInitialProps`。 +当页面初始化加载时,`getInitialProps`仅在服务端上执行。只有当路由跳转(`Link`组件跳转或 API 方法跳转)时,客户端才会执行`getInitialProps`。 -注意:`getInitialProps`将不能使用在子组件中。只能使用在`pages`页面中。 +注意:`getInitialProps`将不能在子组件中使用。只能在`pages`页面中使用。
@@ -348,14 +347,17 @@ export default Page; - `pathname` - URL 的 path 部分 - `query` - URL 的 query 部分,并被解析成对象 - `asPath` - 显示在浏览器中的实际路径(包含查询部分),为`String`类型 -- `req` - HTTP 请求对象 (只有服务器端有) -- `res` - HTTP 返回对象 (只有服务器端有) +- `req` - HTTP 请求对象 (仅限服务器端) +- `res` - HTTP 返回对象 (仅限服务器端) +- `jsonPageRes` - 获取响应对象(仅限客户端) - `err` - 渲染过程中的任何错误 ### 路由 +Next.js不会随应用程序中每个可能的路由一起发布路由清单,因此当前页面不知道客户端上的任何其他页面。出于可扩展性考虑,所有后续路由都会惰性加载。 + #### ``用法 @@ -369,32 +371,84 @@ export default Page; 可以用 `` 组件实现客户端的路由切换。 +**基本例子** + +参考下面的两个页面: + ```jsx // pages/index.js -import Link from "next/link"; +import Link from 'next/link' -export default () => ( -
- Click{" "} - - here - {" "} - to read more -
-); +function Home() { + return ( +
+ Click{' '} + + here + {' '} + to read more +
+ ) +} + +export default Home ``` ```jsx // pages/about.js -export default () =>

Welcome to About!

; +function About() { + return

Welcome to About!

+} + +export default About ``` -注意:可以使用[``](#prefetching-pages)使链接和预加载在后台同时进行,来达到页面的最佳性能。 +**自定义路由 (使用URL中的props)** + +`` 组件有两个主要属性: + +- `href`: `pages`目录内的路径+查询字符串. +- `as`: 将在浏览器URL栏中呈现的路径. + +例子: + +1. 假设你有个这样的路由 `/post/:slug`. + +2. 你可以创建文件 `pages/post.js` + +```jsx +class Post extends React.Component { + static async getInitialProps({ query }) { + console.log('SLUG', query.slug) + return {} + } + render() { + return

My blog post

+ } +} + +export default Post +``` + +3. 将路由添加到 `express` (或者其他服务端) 的 `server.js` 文件 (这仅适用于SSR). 这将解析`/post/:slug`到`pages/post.js`并在getInitialProps中提供`slug`作为查询的一部分。 + +```jsx +server.get('/post/:slug', (req, res) => { + return app.render(req, res, '/post', { slug: req.params.slug }) +}) +``` + +4. 对于客户端路由,使用 `next/link`: +```jsx + +``` + +_注意:可以使用[``](#prefetching-pages)使链接和预加载在后台同时进行,来达到页面的最佳性能。_ 客户端路由行为与浏览器很相似: -1. 组件获取 -2. 如果组件定义了`getInitialProps`,数据获取了。如果有错误情况将会渲染 `_error.js`。 +1. 获取组件 +2. 如果组件定义了`getInitialProps`,则获取数据。如果有错误情况将会渲染 `_error.js`。 3. 1 和 2 都完成了,`pushState`执行,新组件被渲染。 如果需要注入`pathname`, `query` 或 `asPath`到你组件中,你可以使用[withRouter](#using-a-higher-order-component)。 @@ -552,12 +606,12 @@ Router.beforePopState(({ url, as, options }) => { 以上`Router`对象的 API 如下: -- `route` - 当前路由的`String`类型 +- `route` - 当前路由,为`String`类型 - `pathname` - 不包含查询内容的当前路径,为`String`类型 - `query` - 查询内容,被解析成`Object`类型. 默认为`{}` - `asPath` - 展现在浏览器上的实际路径,包含查询内容,为`String`类型 -- `push(url, as=url)` - 页面渲染第一个参数 url 的页面,浏览器栏显示的是第二个参数 url -- `replace(url, as=url)` - performs a `replaceState` call with the given url +- `push(url, as=url)` - 用给定的url调用`pushState` +- `replace(url, as=url)` - 用给定的url调用`replaceState` - `beforePopState(cb=function)` - 在路由器处理事件之前拦截. `push` 和 `replace` 函数的第二个参数`as`,是为了装饰 URL 作用。如果你在服务器端设置了自定义路由将会起作用。 @@ -591,7 +645,7 @@ export default () => ( ##### 路由事件 你可以监听路由相关事件。 -下面是事件支持列表: +下面是支持的事件列表: - `routeChangeStart(url)` - 路由开始切换时触发 - `routeChangeComplete(url)` - 完成路由切换时触发 @@ -612,13 +666,13 @@ const handleRouteChange = url => { Router.events.on("routeChangeStart", handleRouteChange); ``` -如果你不想长期监听该事件,你可以用`off`事件去取消监听: +如果你不想再监听该事件,你可以用`off`事件去取消监听: ```js Router.events.off("routeChangeStart", handleRouteChange); ``` -如果路由加载被取消(比如快速连续双击链接) +如果路由加载被取消(比如快速连续双击链接),`routeChangeError`将触发。传递err,并且属性cancelled的值为true。 ```js Router.events.on("routeChangeError", (err, url) => { @@ -966,7 +1020,7 @@ ext.js 支持 JavaScript 的 TC39 提议[dynamic import proposal](https://github -#### 1. 基础支持 (同样支持 SSR) +#### 1. 基础用法 (也就是SSR) ```jsx import dynamic from "next/dynamic"; @@ -1151,10 +1205,44 @@ export default class MyDocument extends Document { +#### 自定义 `renderPage` + +🚧 应该注意的是,您应该定制“renderPage”的唯一原因是使用css-in-js库,需要将应用程序包装起来以正确使用服务端渲染。 🚧 + +- 它将一个选项对象作为参数进行进一步的自定义: + +```js +import Document from 'next/document' + +class MyDocument extends Document { + static async getInitialProps(ctx) { + const originalRenderPage = ctx.renderPage + + ctx.renderPage = () => + originalRenderPage({ + // useful for wrapping the whole react tree + enhanceApp: App => App, + // useful for wrapping in a per-page basis + enhanceComponent: Component => Component + }) + + // Run the parent `getInitialProps` using `ctx` that now includes our custom `renderPage` + const initialProps = await Document.getInitialProps(ctx) + + return initialProps + } +} + +export default MyDocument +``` + + ### 自定义错误处理 404 和 500 错误客户端和服务端都会通过`error.js`组件处理。如果你想改写它,则新建`_error.js`在文件夹中: +⚠️ 该`pages/_error.js`组件仅用于生产。在开发过程中,您会收到调用堆栈错误,以了解错误源自何处。 ⚠️ + ```jsx import React from "react"; @@ -1300,7 +1388,7 @@ module.exports = { -#### 配置页面后缀名解析扩展 +#### 配置解析路由时的页面文件后缀名 如 typescript 模块[`@zeit/next-typescript`](https://github.com/zeit/next-plugins/tree/master/packages/next-typescript),需要支持解析后缀名为`.ts`的文件。`pageExtensions` 允许你扩展后缀名来解析各种 pages 下的文件。 @@ -1472,6 +1560,45 @@ presets / plugins 不允许添加到`.babelrc`中,然而你可以配置`next/b ### 暴露配置到服务端和客户端 +在应用程序中通常需要提供配置值 + +Next.js支持2种提供配置的方式: + +- 构建时配置 +- 运行时配置 + +#### 构建时配置 + +构建时配置的工作方式是将提供的值内联到Javascript包中。 + +你可以在`next.config.js`设置`env`: + +```js +// next.config.js +module.exports = { + env: { + customKey: 'value' + } +} +``` + +这将允许你在代码中使用`process.env.customKey`,例如: + +```jsx +// pages/index.js +function Index() { + return

The value of customKey is: {process.env.customKey}

+} + +export default Index +``` + +#### 运行时配置 + +> ⚠️ 请注意,使用此选项时不可用 `target: 'serverless'` + +> ⚠️ 通常,您希望使用构建时配置来提供配置。原因是运行时配置增加了一个小的rendering/initialization开销。 + `next/config`模块使你应用运行时可以读取些存储在`next.config.js`的配置项。`serverRuntimeConfig`属性只在服务器端可用,`publicRuntimeConfig`属性在服务端和客户端可用。 ```js @@ -1479,34 +1606,36 @@ presets / plugins 不允许添加到`.babelrc`中,然而你可以配置`next/b module.exports = { serverRuntimeConfig: { // Will only be available on the server side - mySecret: "secret" + mySecret: 'secret', + secondSecret: process.env.SECOND_SECRET // Pass through env variables }, publicRuntimeConfig: { // Will be available on both server and client - staticFolder: "/static", - mySecret: process.env.MY_SECRET // Pass through env variables + staticFolder: '/static' } -}; +} ``` ```js // pages/index.js -import getConfig from "next/config"; +import getConfig from 'next/config' // Only holds serverRuntimeConfig and publicRuntimeConfig from next.config.js nothing else. -const { serverRuntimeConfig, publicRuntimeConfig } = getConfig(); +const { serverRuntimeConfig, publicRuntimeConfig } = getConfig() -console.log(serverRuntimeConfig.mySecret); // Will only be available on the server side -console.log(publicRuntimeConfig.staticFolder); // Will be available on both server and client +console.log(serverRuntimeConfig.mySecret) // Will only be available on the server side +console.log(publicRuntimeConfig.staticFolder) // Will be available on both server and client -export default () => ( -
- logo -
-); +function MyImage() { + return ( +
+ logo +
+ ) +} + +export default MyImage ``` - - ### 启动服务选择 hostname 启动开发环境服务可以设置不同的 hostname,你可以在启动命令后面加上`--hostname 主机名` 或 `-H 主机名`。它将会启动一个 TCP 服务器来监听连接所提供的主机。 @@ -1565,6 +1694,73 @@ Next.js 也有其他托管解决方案。请查考 wiki 章节['Deployment'](htt + +### 无服务器部署 + +
+ 例子 + +
+ +无服务器部署通过将应用程序拆分为更小的部分(也称为[**lambdas**](https://zeit.co/docs/v2/deployments/concepts/lambdas/))来显着提高可靠性和可伸缩性。在Next.js中,`pages`目录中的每个页面都变成了无服务器的lambda。 +对于无服务器的人来说,有[许多好处](https://zeit.co/blog/serverless-express-js-lambdas-with-now-2#benefits-of-serverless-express)。引用的链接在Express的上下文中讨论了其中的一些,但这些原则普遍适用:无服务器允许分布式故障点,无限的可扩展性,并且通过“为您使用的内容付费”的模式来提供难以置信的价格。 + +要在Next.js中启用**无服务器模式**,可在`Next.config.js`中配置`target`值为`serverless`: + +```js +// next.config.js +module.exports = { + target: 'serverless' +} +``` + +`serverless`将每页输出一个lambda。此文件是完全独立的,不需要运行任何依赖项: + +- `pages/index.js` => `.next/serverless/pages/index.js` +- `pages/about.js` => `.next/serverless/pages/about.js` + +Next.js无服务器功能的签名类似于Node.js HTTP服务器回调: + +```ts +export function render(req: http.IncomingMessage, res: http.ServerResponse) => void +``` + +- [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) +- [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) +- `void` 指的是没有返回值的函数,它等同于JavaScript`undefined`。调用该函数将完成请求。 + +使用无服务配置, 你可以讲Next.js部署到[ZEIT Now](https://zeit.co/now) 并提供所有的好处和易于控制; [custom routes](https://zeit.co/guides/custom-next-js-server-to-routes/) 缓存头. 要了解更多信息,请参阅 [ZEIT Guide for Deploying Next.js with Now](https://zeit.co/guides/deploying-nextjs-with-now/) + +#### 降级部署 + +Next.js为无服务器部署提供低级API,因为托管平台具有不同的功能签名。通常,您需要使用兼容性层包装Next.js无服务器构建的输出。 + +例如,如果平台支持Node.js[`http.Server`](https://nodejs.org/api/http.html#http_class_http_server)类: + +```js +const http = require('http') +const page = require('./.next/serverless/pages/about.js') +const server = new http.Server((req, res) => page.render(req, res)) +server.listen(3000, () => console.log('Listening on http://localhost:3000')) +``` + +有关特定平台示例,请参阅[the examples section above](#serverless-deployment). + +#### 摘要 + +- 用于实现无服务器部署的Low-level API +- `pages`目录中的每个页面都成为无服务器功能(lambda) +- 创建最小的无服务器功能 (50Kb base zip size) +- 针对功能的快速[cold start](https://zeit.co/blog/serverless-ssr#cold-start) 进行了优化 +- 无服务器函数有0个依赖项 (依赖项包含在函数包中) +- 使用Node.js中的[http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)和[http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) +- 选择使用`target: 'serverless'` in `next.config.js` +- 在执行函数时不要加载`next.config.js`,请注意这意味着`publicRuntimeConfig` / `serverRuntimeConfig`不支持。 + ## 浏览器支持 Next.js 支持 IE11 和所有的现代浏览器使用了[`@babel/preset-env`](https://new.babeljs.io/docs/en/next/babel-preset-env.html)。为了支持 IE11,Next.js 需要全局添加`Promise`的 polyfill。有时你的代码或引入的其他 NPM 包的部分功能现代浏览器不支持,则需要用 polyfills 去实现。 @@ -1661,6 +1857,38 @@ now +### 复制自定义文件 + +如果您必须复制robots.txt等自定义文件或生成sitemap.xml,您可以在其中执行此操作`exportPathMap`。 `exportPathMap`获取一些上下文参数来帮助您创建/复制文件: + +- `dev` - `true`表示在开发环境下使用`exportPathMap`. `false`表示运行于`next export`. 在开发中,“exportpathmap”用于定义路由,不需要复制文件等行为。 +- `dir` - 项目目录的绝对路径 +- `outDir` - 指向`out`目录的绝对路径(可配置为`-o`或`--outdir`)。当`dev`为`true`时,`outdir`的值将为`null`。 +- `distDir` - `.next`目录的绝对路径(可使用`distDir`配置键配置) +- `buildId` - 导出正在运行的buildId + +```js +// next.config.js +const fs = require('fs') +const { join } = require('path') +const { promisify } = require('util') +const copyFile = promisify(fs.copyFile) + +module.exports = { + exportPathMap: async function( + defaultPathMap, + { dev, dir, outDir, distDir, buildId } + ) { + if (dev) { + return defaultPathMap + } + // This will copy robots.txt from your project root into the out directory + await copyFile(join(dir, 'robots.txt'), join(outDir, 'robots.txt')) + return defaultPathMap + } +} +``` + ### 限制 使用`next export`,我们创建了个静态 HTML 应用。构建时将会运行页面里生命周期`getInitialProps` 函数。