Next.js v4.1.4 文档中文翻译

最近想稍稍看下 ReactSSR框架 Next.js,因为不想看二手资料, 所以自己跑到 Github上看,Next.js的文档是英文的,看倒是大概也能看得懂, 但有些地方不太确定,而且英文看着毕竟不太爽你懂得,所以在网上搜了几圈发现好像好像还没有中文翻译,想着长痛不如短痛, 索性一边看一边翻译,自己翻译的东西自己看得也爽,不过毕竟能力有限,有些地方我也不知道该怎么翻译才好,所以翻译得不太通畅, 或者有几句干脆不翻译了。

so,各位若是觉得我哪点翻译得不太准确,或者对于那几句我没翻译的地方有更好的见解,欢迎提出~

以下是全文翻译的 Next.jsREADME.md文件,版本是 v4.1.4,除了翻译原文之外,还加了一点个人小小见解。

另外,没太弄明白掘金写文章的md页面内超链接的语法是什么,所以下面的目录超链接没有效果,不过不影响阅读,想要更好的阅读体验可以去我的 github上看,别忘了 star哦~


Next.js v4.1.4 文档中文翻译

Next.js是一个用于React应用的极简的服务端渲染框架。

请访问 learnnextjs.com 以获取更多详细内容.


如何使用

安装

安装方法:


npm install --save next react react-dom

Next.js 4 只支持 React 16.
由于 React 16 和 React 15 的工作方式以及使用方法不尽相同,所以我们不得不移除了对 React 15 的支持

在你的 package.json文件中添加如下代码:


{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}

接下来,大部分事情都交由文件系统来处理。每个 .js 文件都变成了一个自动处理和渲染的路由。


在项目中新建 ./pages/index.js


export default () => <div>Welcome to next.js!</div>

然后,在控制台输入 npm run dev命令,打开 http://localhost:3000即可看到程序已经运行,你当然也可以使用其他的端口号,可以使用这条命令:npm run dev -- -p <your port here>

目前为止,我们已经介绍了:

  • 自动编译和打包 (使用 webpackbabel)
  • 代码热更新
  • ./pages目录作为页面渲染目录的的服务器端渲染
  • 静态文件服务(./static/ 被自动定位到 /static/)

想要亲自试试这些到底有多简单, check out sample app - nextgram

代码自动分割

你所声明的每个 import命令所导入的文件会只会与相关页面进行绑定并提供服务,也就是说,页面不会加载不需要的代码。


import cowsay from 'cowsay-browser'

export default () =>
  <pre>
    {cowsay.say({ text: 'hi there!' })}
  </pre>

CSS

嵌入式样式 Built-in-CSS

Examples

我们提供 style-jsx来支持局部独立作用域的 CSS(scope CSS),目的是提供一种类似于 Web组件的 shadow CSS,不过,后者(即shadow CSS)并不支持服务器端渲染(scope CSS是支持的)。



export default () =>
  <div>
    Hello world
    <p>scoped!</p>
    <style jsx>{`
      p {
        color: blue;
      }
      div {
        background: red;
      }
      @media (max-width: 600px) {
        div {
          background: blue;
        }
      }
    `}</style>
    <style global jsx>{`
      body {
        background: black;
      }
    `}</style>
  </div>

更多示例可见 styled-jsx documentation

译者注:

  1. scope CSS的作用范围,如果添加了 jsx属性,则是不包括子组件的当前组件;如果添加了 globaljsx属性,则是包括了子组件在内的当前组件;如果没添加任何属性,则作用与 添加了 globaljsx的作用类似,只不过 next不会对其进行额外的提取与优化打包
  2. scope CSS的实现原理,其实就是在编译好的代码的对应元素上,添加一个以 jsx开头的类名(class),然后将对应的样式代码提取到此类名下

内联式样式 CSS-in-JS

Examples

几乎可以使用所有的内联样式解决方案,最简单一种如下:



export default () => <p style={{ color: 'red' }}>hi there</p>

为了使用更多复杂的 CSS-in-JS 内联样式方案,你可能不得不在服务器端渲染的时候强制样式刷新。我们通过允许自定义包裹着每个页面的 <Document> 组件的方式来解决此问题。

静态文件服务

在你的项目的根目录新建 static 文件夹,然后你就可以在你的代码通过 /static/ 开头的路径来引用此文件夹下的文件:


export default () => <img src="/static/my-image.png" />

自定义 <head> 头部元素

Examples

我们暴露了一个用于将元素追加到 <head> 中的组件。



import Head from 'next/head'

export default () =>
  <div>
    <Head>
      <title>My page title</title>
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    </Head>
    <p>Hello world!</p>
  </div>

注意:当组件卸载的时候,组件内定义的 <Head>将会被清空,所以请确保每个页面都在其各自的 <Head>内声明了其所有需要的内容,而不是假定这些东西已经在其他页面中添加过了。

译者注:

  1. next 框架自带 <head>标签,作为当前页面的 <head>,如果在组件内自定义了 <Head>,则自定义 <Head>内的元素(例如 <title><meta>等)将会被追加到框架自带的 <head>标签中
  2. 每个组件自定义的 <Head>内容只会应用在各自的页面上,子组件内定义的 <Head>也会追加到当前页面的 <head>内,如果有重复定义的标签或属性,则子组件覆盖父组件,位于文档更后面的组件覆盖更前面的组件。

数据获取及组件生命周期

Examples

你可以通过导出一个基于 React.Component的组件来获取状态(state)、生命周期或者初始数据(而不是一个无状态函数(stateless),就像上面的一段代码)


import React from 'react'

export default class extends React.Component {
  static async getInitialProps({ req }) {
    const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
    return { userAgent }
  }

  render() {
    return (
      <div>
        Hello World {this.props.userAgent}
      </div>
    )
  }
}


你可能已经注意到了,当加载页面获取数据的时候,我们使用了一个异步(async)的静态方法 getInitialProps。此静态方法能够获取所有的数据,并将其解析成一个 JavaScript对象,然后将其作为属性附加到 props对象上。

当初始化页面的时候,getInitialProps只会在服务器端执行,而当通过 Link组件或者使用命令路由 API来将页面导航到另外一个路由的时候,此方法就只会在客户端执行。

注意:getInitialProps 不能 在子组件上使用,只能应用于当前页面的顶层组件。


如果你在 getInitialProps 中引入了一些只能在服务器端使用的模块(例如一些 node.js的核心模块),请确保通过正确的方式来导入它们 import them properly,否则的话,那很可能会拖慢应用的速度。


你也可以为无状态(stateless)组件自定义 getInitialProps生命周期方法:



const Page = ({ stars }) =>
  <div>
    Next stars: {stars}
  </div>

Page.getInitialProps = async ({ req }) => {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
}

export default Page

getInitialProps 接收的上下文对象包含以下属性:

  • pathname - URLpath部分
  • query - URLquery string部分,并且其已经被解析成了一个对象
  • asPath - 在浏览器上展示的实际路径(包括 query字符串)
  • req - HTTP request 对象 (只存在于服务器端)
  • res - HTTP response 对象 (只存在于服务器端)
  • jsonPageRes - 获取的响应数据对象 Fetch Response (只存在于客户端)
  • err - 渲染时发生错误抛出的错误对象

译者注: 基于 getInitialProps在服务器端和客户端的不同表现,例如 req的存在与否,可以通过此来区分服务器端和客户端。

路由

<Link>

Examples

可以通过 <Link> 组件来实现客户端在两个路由间的切换功能,例如下面两个页面



// pages/index.js
import Link from 'next/link'

export default () =>
  <div>
    Click{' '}
    <Link href="/about">
      <a>here</a>
    </Link>{' '}
    to read more
  </div>


// pages/about.js
export default () => <p>Welcome to About!</p>

注意:可以使用 <Link prefetch> 来让页面在后台同时获取和预加载,以获得最佳的页面加载性能

客户端路由行为与浏览器完全相同:

  1. 获取组件
  2. 如果组件定义了 getInitialProps,那么进行数据的获取,如果抛出异常,则将渲染_error.js
  3. 在步骤1和步骤2完成后,pushState开始执行,接着新组件将会被渲染

每一个顶层组件都会接收到一个 url属性,其包括了以下 API:

  • pathname - 不包括 query字符串在内的当前链接地址的 path字符串(即pathname)
  • query - 当前链接地址的 query字符串,已经被解析为对象,默认为 {}
  • asPath - 在浏览器地址栏显示的当前页面的实际地址(包括 query字符串)
  • push(url, as=url) - 通过 pushState来跳转路由到给定的 url
  • replace(url, as=url) - 通过 replaceState来将当前路由替换到给定的路由地址 url

push 以及 replace的第二个参数 as提供了额外的配置项,当你在服务器上配置了自定义路由的话,那么此参数就会发挥作用。

译者注1: 上面那句话的意思是,as可以根据服务器端路由的配置作出相应的 路由改变,例如,在服务器端,你自定义规定当获取 /apath请求的时候,返回一个位于 /b目录下的页面,则为了配合服务器端的这种指定,你可以这么定义 <Link/>组件: <Link href='/a' as='/b'><a>a</a></Link> 这种做法有一个好处,那就是尽管你将 /a请求指定到了 /b页面,但是因为as的值为 /a,所以编译后的 DOM元素显示的链接的 href值为 /a,但是当真正点击链接时,响应的真正页面还是 /b


译者注2: <Link>组件主要用于路由跳转功能,其可以接收一个必须的子元素(DOM标签或者纯文字等)

  1. 如果添加的子元素是 DOM元素,则 Link会为此子元素赋予路由跳转功能;
  2. 如果添加的元素是纯文字,则 <Link>默认转化为 a标签,包裹在此文字外部(即作为文字的父元素),如果当前组件有 jsx属性的 scope CSS,这个 a标签是不会受此 scope CSS影响的,也就是说,不会加上以 jsx开头的类名。
    需要注意的是,直接添加纯文字作为子元素的做法如今已经不被赞成了(deprecated)。
URL 对象

Examples

<Link>组件可以接收一个 URL对象,此 URL对象将会被自动格式化为 URL字符串。



// pages/index.js
import Link from 'next/link'

export default () =>
  <div>
    Click{' '}
    <Link href={{ pathname: '/about', query: { name: 'Zeit' } }}>
      <a>here</a>
    </Link>{' '}
    to read more
  </div>

上述代码中 <Link>组件的将会根据 href属性的对象值生成一个 /about?name=ZeitURL字符串,你也可以在此 URL对象中使用任何已经在 Node.js URL module documentation 中定义好了的属性来配置路由。

替换 (replace)而非追加(push)路由 url

<Link>组件默认将新的 URL追加 (push)到路由栈中,但你可以使用 replace属性来避免此追加动作(直接替换掉当前路由)。


// pages/index.js
import Link from 'next/link'

export default () =>
  <div>
    Click{' '}
    <Link href="/about" replace>
      <a>here</a>
    </Link>{' '}
    to read more
  </div>

让组件支持 onClick事件

<Link> supports any component that supports the onClick event. In case you don't provide an <a> tag, it will only add the onClick event handler and won't pass the href property. <Link>标签支持所有支持 onClick事件的组件(即只要某组件或者元素标签支持 onClick事件,则 <Link>就能够为其提供跳转路由的功能)。如果你没有给 <Link>标签添加一个 <a>标签的子元素的话,那么它只会执行给定的 onClick事件,而不是执行跳转路由的动作。



// pages/index.js
import Link from 'next/link'

export default () =>
  <div>
    Click{' '}
    <Link href="/about">
      <img src="/static/image.png" />
    </Link>
  </div>

<Link>href暴露给其子元素(child)

如果 <Link>的子元素是一个 <a>标签并且没有指定 href属性的话,那么我们会自动指定此属性(与 <Link>herf相同)以避免重复工作,然而有时候,你可能想要通过一个被包裹在某个容器(例如组件)内的 <a>标签来实现跳转功能,但是 Link并不认为那是一个超链接,因此,就不会把它的 href属性传递给子元素,为了避免此问题,你应该给 Link附加一个 passHref属性,强制让 Link将其 href属性传递给它的子元素。


import Link from 'next/link'
import Unexpected_A from 'third-library'

export default ({ href, name }) =>
  <Link href={href} passHref>
    <Unexpected_A>
      {name}
    </Unexpected_A>
  </Link>

命令式路由

Examples

你可以使用 next/router来实现客户端侧的页面切换


import Router from 'next/router'

export default () =>
  <div>
    Click <span onClick={() => Router.push('/about')}>here</span> to read more
  </div>

上述代码中的 Router对象拥有以下 API

  • route - 当前路由字符串
  • pathname - 不包括查询字符串(query string)在内的当前路由的 path(也就是 pathname)
  • query - Object with the parsed query string. Defaults to {}
  • asPath - 在浏览器地址栏显示的当前页面的实际地址(包括 query字符串)
  • push(url, as=url) - 通过 pushState来跳转路由到给定的 url
  • replace(url, as=url) - 通过 replaceState来将当前路由替换到给定的路由地址 url

push 以及 replace的第二个参数 as提供了额外的配置项,当你在服务器上配置了自定义路由的话,那么此参数就会发挥作用。

为了使用编程的方式而不是触发导航和组件获取的方式来切换路由,可以在组件内部使用 props.url.pushprops.url.replace

译者注: 除非特殊需要,否则在组件内部不赞成(deprecated)使用 props.url.pushprops.url.replace,而是建议使用 next/router的相关 API

URL 对象

命令式路由 (next/router)所接收的 URL对象与 <Link>URL对象很类似,你可以使用相同的方式来pushreplace路由URL


import Router from 'next/router'

const handler = () =>
  Router.push({
    pathname: '/about',
    query: { name: 'Zeit' }
  })

export default () =>
  <div>
    Click <span onClick={handler}>here</span> to read more
  </div>

命令式路由 (next/router)的 URL对象的属性及其参数的使用方法和 <Link>组件的完全一样。

路由事件

你还可以监听到与 Router相关的一些事件。

以下是你所能够监听的 Router事件:

  • routeChangeStart(url) - 当路由刚开始切换的时候触发
  • routeChangeComplete(url) - 当路由切换完成时触发
  • routeChangeError(err, url) - 当路由切换发生错误时触发
  • beforeHistoryChange(url) - 在改变浏览器 history之前触发
  • appUpdated(nextRoute) - 当切换页面的时候,应用版本刚好更新的时触发(例如在部署期间切换路由)

Here url is the URL shown in the browser. If you call Router.push(url, as) (or similar), then the value of url will be as. 上面 API中的 url参数指的是浏览器地址栏显示的链接地址,如果你使用 Router.push(url, as)(或者类似的方法)来改变路由,则此值就将是 as的值

下面是一段如何正确地监听路由事件 routeChangeStart的示例代码:


Router.onRouteChangeStart = url => {
  console.log('App is changing to: ', url)
}

如果你不想继续监听此事件了,那么你也可以很轻松地卸载掉此监听事件,就像下面这样:

Router.onRouteChangeStart = null

如果某个路由加载被取消掉了(例如连续快速地单击两个链接),routeChangeError 将会被执行。此方法的第一个参数 err对象中将包括一个值为 true的 cancelled属性。

Router.onRouteChangeError = (err, url) => {
  if (err.cancelled) {
    console.log(`Route to ${url} was cancelled!`)
  }
}

如果你在一次项目新部署的过程中改变了路由,那么我们就无法在客户端对应用进行导航,必须要进行一次完整的导航动作(译者注:意思是无法像正常那样通过 PWA的方式进行导航),我们已经自动帮你做了这些事。 不过,你也可以通过 Route.onAppUpdated事件对此进行自定义操作,就像下面这样:


Router.onAppUpdated = nextUrl => {
  // persist the local state
  location.href = nextUrl
}

译者注:
一般情况下,上述路由事件的发生顺序如下:

  1. routeChangeStart
  2. beforeHistoryChange
  3. routeChangeComplete
浅层路由

Examples

浅层路由(Shallow routing)允许你在不触发 getInitialProps的情况下改变路由(URL),你可以通过要加载页面的 url来获取更新后的 pathnamequery,这样就不会丢失路由状态(state)了。

你可以通过调用 Router.pushRouter.replace,并给它们加上 shallow: true的配置参数来实现此功能,下面是一个使用示例:



// Current URL is "/"
const href = '/?counter=10'
const as = href
Router.push(href, as, { shallow: true })

现在,URL已经被更新到了 /?counter=10,你可以在组件内部通过 this.props.url来获取此 URL

你可以在 componentWillReceiveProps钩子函数中获取到 URL的变化,就像下面这样:

componentWillReceiveProps(nextProps) {
  const { pathname, query } = nextProps.url
  // fetch data based on the new query
}

注意:

浅层路由只会在某些页面上起作用,例如,我们可以假定存在另外一个名为 about的页面,然后执行下面这行代码:

Router.push('/about?counter=10', '/about?counter=10', { shallow: true })
复制代码

因为这是一个新的页面(/about?counter=10),所以即使我们已经声明了只执行浅层路由,但当前页面仍然会被卸载掉(unload),然后加载这个新的页面并调用 getInitialProps方法

使用高阶函数 HOC

Examples

如果你想在应用的任何组件都能获取到 router对象,那么你可以使用 withRouter高阶函数,下面是一个使用此高阶函数的示例:


import { withRouter } from 'next/router'

const ActiveLink = ({ children, router, href }) => {
  const style = {
    marginRight: 10,
    color: router.pathname === href? 'red' : 'black'
  }

  const handleClick = (e) => {
    e.preventDefault()
    router.push(href)
  }

  return (
    <a href={href} onClick={handleClick} style={style}>
      {children}
    </a>
  )
}

export default withRouter(ActiveLink)

上述代码中的 router对象拥有和 next/router 相同的 API

预获取页面 Prefetching Pages

(下面就是一个小例子)

Examples

Next.js自带允许你预获取(prefetch)页面的 API

因为 Next.js在服务器端渲染页面,所以应用的所有将来可能发生交互的相关链接路径可以在瞬间完成交互,事实上 Next.js可以通过预下载功能来达到一个绝佳的加载性能。[更多详细可见](Read more.)

由于 Next.js只会预加载 JS代码,所以在页面加载的时候,你可以还需要花点时间来等待数据的获取。

通过 <Link> 组件

你可以为任何一个 <Link>组件添加 prefetch属性,Next.js将会在后台预加载这些页面。



import Link from 'next/link'

// example header component
export default () =>
  <nav>
    <ul>
      <li>
        <Link prefetch href="/">
          <a>Home</a>
        </Link>
      </li>
      <li>
        <Link prefetch href="/about">
          <a>About</a>
        </Link>
      </li>
      <li>
        <Link prefetch href="/contact">
          <a>Contact</a>
        </Link>
      </li>
    </ul>
  </nav>

通过命令的方式

大部分预获取功能都需要通过 <Link>组件来指定链接地址,但是我们还暴露了一个命令式的 API以方便更加复杂的场景:


import Router from 'next/router'

export default ({ url }) =>
  <div>
    <a onClick={() => setTimeout(() => url.pushTo('/dynamic'), 100)}>
      A route transition will happen after 100ms
    </a>
    {// but we can prefetch it!
    Router.prefetch('/dynamic')}
  </div>

自定义服务器和路由

Examples

一般来说,你可以使用 next start命令启动 next服务,但是,你也完全可以使用编程(programmatically)的方式,例如路由匹配等,来定制化路由。

下面就是一个将 /a匹配到 ./page/b,以及将 /b匹配到 ./page/a的例子:



// This file doesn't not go through babel or webpack transformation.
// Make sure the syntax and sources this file requires are compatible with the current node version you are running
// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  createServer((req, res) => {
    // Be sure to pass `true` as the second argument to `url.parse`.
    // This tells it to parse the query portion of the URL.
    const parsedUrl = parse(req.url, true)
    const { pathname, query } = parsedUrl

    if (pathname === '/a') {
      app.render(req, res, '/b', query)
    } else if (pathname === '/b') {
      app.render(req, res, '/a', query)
    } else {
      handle(req, res, parsedUrl)
    }
  }).listen(3000, err => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})

next API如下所示:

  • next(path: string, opts: object) - pathNext应用当前的路由位置
  • next(opts: object)

上述 API中的 opt对象存在如下属性:

  • dev (bool) 是否使用开发模式(dev)来启动 Next.js - 默认为 false
  • dir (string) 当前 Next应用的路由位置 - 默认为 '.'
  • quiet (bool) 隐藏包括服务器端消息在内的错误消息 - 默认为 false
  • conf (object) 和next.config.js 中的对象是同一个 - 默认为 {}

然后,将你(在 package.json中配置)的 start命令(script)改写成 NODE_ENV=production node server.js

异步导入 Dynamic Import

Examples

Next.js支持 JavaScript TC39dynamic import proposal规范,所以你可以动态导入(import) JavaScript 模块(例如 React Component)。

你可以将动态导入理解为一种将代码分割为更易管理和理解的方式。 由于 Next.js支持服务器端渲染侧(SSR)的动态导入,所以你可以用它来做一些炫酷的东西。

1. 基本用法(同样支持 SSR

import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(import('../components/hello'))

export default () =>
  <div>
    <Header />
    <DynamicComponent />
    <p>HOME PAGE is here!</p>
  </div>

2. 自定义 加载组件


import dynamic from 'next/dynamic'

const DynamicComponentWithCustomLoading = dynamic(
  import('../components/hello2'),
  {
    loading: () => <p>...</p>
  }
)

export default () =>
  <div>
    <Header />
    <DynamicComponentWithCustomLoading />
    <p>HOME PAGE is here!</p>
  </div>

3. 禁止 SSR


import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(import('../components/hello3'), {
  ssr: false
})

export default () =>
  <div>
    <Header />
    <DynamicComponentWithNoSSR />
    <p>HOME PAGE is here!</p>
  </div>

4. 一次性加载多个模块


import dynamic from 'next/dynamic'

const HelloBundle = dynamic({
  modules: props => {
    const components = {
      Hello1: import('../components/hello1'),
      Hello2: import('../components/hello2')
    }

    // Add remove components based on props

    return components
  },
  render: (props, { Hello1, Hello2 }) =>
    <div>
      <h1>
        {props.title}
      </h1>
      <Hello1 />
      <Hello2 />
    </div>
})

export default () => <HelloBundle title="Dynamic Bundle" />

自定义 <Document>

Examples

Next.js帮你自动跳过了在为页面添加文档标记元素的操作,例如, 你从来不需要主动添加 <html><body>这些文档元素。如果你想重定义这些默认操作的话,那么你可以创建(或覆写) ./page/_ducument.js文件,在此文件中,对 Document进行扩展:



// ./pages/_document.js
import Document, { Head, Main, NextScript } from 'next/document'
import flush from 'styled-jsx/server'

export default class MyDocument extends Document {
  static getInitialProps({ renderPage }) {
    const { html, head, errorHtml, chunks } = renderPage()
    const styles = flush()
    return { html, head, errorHtml, chunks, styles }
  }

  render() {
    return (
      <html>
        <Head>
          <style>{`body { margin: 0 } /* custom! */`}</style>
        </Head>
        <body className="custom_class">
          {this.props.customValue}
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

在以下前提下,所有的 getInitialProps 钩子函数接收到的 ctx都指的是同一个对象:

  • 回调函数 renderPage (Function)是真正执行 React渲染逻辑的函数(同步地),这种做法有助于此函数支持一些类似于 Aphrodite'srenderStatic等一些服务器端渲染容器。

注意:<Main/>之外的 React组件都不会被浏览器初始化,如果你想在所有的页面中使用某些组件(例如菜单栏或者工具栏),首先保证不要在其中添加有关应用逻辑的内容,然后可以看看这个例子

译者注: 上面那句话的意思是,在 _document.js文件中,你可以额外添加其他的一些组件,但是这所有的组件中,除了 <Main/>以外,其他的组件内的所有逻辑都不会被初始化和执行,这些不会被初始化和执行的逻辑代码包括除了 render 之外的所有生命周期钩子函数,例如componnetDidMountcomponentWillUpdate,以及一些监听函数,例如 onClickonMouseOver等,所以如果你要在_document.js添加额外的组件,请确保这些组件中除了 render之外没有其他的逻辑

自定义错误处理

客户端和服务器端都会捕获并使用默认组件 error.js来处理 404500错误。如果你希望自定义错误处理,可以对其进行覆写:



import React from 'react'

export default class Error extends React.Component {
  static getInitialProps({ res, jsonPageRes }) {
    const statusCode = res
      ? res.statusCode
      : jsonPageRes ? jsonPageRes.status : null
    return { statusCode }
  }

  render() {
    return (
      <p>
        {this.props.statusCode
          ? `An error ${this.props.statusCode} occurred on server`
          : 'An error occurred on client'}
      </p>
    )
  }
}

使用内置的错误页面

如果你想使用内置的错误页面,那么你可以通过 next/error来实现:


import React from 'react'
import Error from 'next/error'
import fetch from 'isomorphic-fetch'

export default class Page extends React.Component {
  static async getInitialProps() {
    const res = await fetch('https://api.github.com/repos/zeit/next.js')
    const statusCode = res.statusCode > 200 ? res.statusCode : false
    const json = await res.json()

    return { statusCode, stars: json.stargazers_count }
  }

  render() {
    if (this.props.statusCode) {
      return <Error statusCode={this.props.statusCode} />
    }

    return (
      <div>
        Next stars: {this.props.stars}
      </div>
    )
  }
}

如果你想使用自定义的错误页面,那么你可以导入你自己的错误(_error)页面组件而非内置的 next/error

译者注: 如果你只是想覆写默认的错误页面,那么可以在 /pages下新建一个名为 _error.js的文件,Next将使用此文件来覆盖默认的错误页面

自定义配置

为了对 Next.js进行更复杂的自定义操作,你可以在项目的根目录下(和 pages/以及 package.json属于同一层级)新建一个 next.config.js文件

注意:next.confgi.js是一个标准的 Node.js模块,而不是一个 JSON文件,此文件在 Next项目的服务端以及 build阶段会被调用,但是在浏览器端构建时是不会起作用的。



// next.config.js
module.exports = {
  /* config options here */
}

设置一个自定义的构建(build)目录

你可以自行指定构建打包的输出目录,例如,下面的配置将会创建一个 build目录而不是 .next作为构建打包的输出目录,如果没有特别指定的话,那么默认就是 .next


// next.config.js
module.exports = {
  distDir: 'build'
}

Configuring the onDemandEntries

Next 暴露了一些能够让你自己控制如何部署服务或者缓存页面的配置:


module.exports = {
  onDemandEntries: {
    // 控制页面在内存`buffer`中缓存的时间,单位是 ms
    maxInactiveAge: 25 * 1000,
    // number of pages that should be kept simultaneously without being disposed
    pagesBufferLength: 2,
  }
}

自定义 webpack 配置

Examples


你可以通过 next.config.js中的函数来扩展 webpack的配置


// This file is not going through babel transformation.
// So, we write it in vanilla JS
// (But you could use ES2015 features supported by your Node.js version)

module.exports = {
  webpack: (config, { buildId, dev }) => {
    // Perform customizations to webpack config

    // Important: return the modified config
    return config
  },
  webpackDevMiddleware: config => {
    // Perform customizations to webpack dev middleware config

    // Important: return the modified config
    return config
  }
}

告:不推荐在 webpack的配置中添加一个支持新文件类型(css less svg等)的 loader,因为 webpack只会打包客户端代码,所以(loader)不会在服务器端的初始化渲染中起作用。Babel是一个很好的替代品,因为其给服务器端和客户端提供一致的功能效果(例如,babel-plugin-inline-react-svg)。

自定义 Babel 配置

Examples

为了扩展对 Babel的使用,你可以在应用的根目录下新建 .babelrc文件,此文件是非必须的。 如果此文件存在,那么我们就认为这个才是真正的Babel配置文件,因此也就需要为其定义一些 next项目需要的东西, 并将之当做是next/babel的预设配置(preset) 这种设计是为了避免你有可能对我们能够定制 babel配置而感到诧异。

下面是一个 .babelrc文件的示例:



{
  "presets": ["next/babel", "stage-0"]
}

CDN 支持

你可以设定 assetPrefix项来配置 CDN源,以便能够与 Next.js项目的 host保持对应。


const isProd = process.env.NODE_ENV === 'production'
module.exports = {
  // You may only need to add assetPrefix in the production.
  assetPrefix: isProd ? 'https://cdn.mydomain.com' : ''
}

注意:Next.js将会自动使用所加载脚本的 CDN域(作为项目的 CDN域),但是对 /static目录下的静态文件就无能为力了。如果你想让那些静态文件也能用上CDN,那你就不得不要自己指定 CDN域,有种方法也可以让你的项目自动根据运行环境来确定 CDN域,可以看看这个例子

项目部署

构建打包和启动项目被分成了以下两条命令:



next build
next start

例如,你可以像下面这样为 now项目配置 package.json文件:

{
  "name": "my-app",
  "dependencies": {
    "next": "latest"
  },
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}

然后就可以直接启动 now项目了!

Next.js也可以使用其他的托管方案,更多详细可以看一下这部分内容 'Deployment' 注意:我们推荐你推送 .next,或者你自定义的打包输出目录(到托管方案上)(Please have a look at 'Custom Config',你还可以自定义一个专门用于放置配置文件(例如 .npmignore.gitignore)的文件夹。否则的话,使用 files或者 now.files来选择要部署的白名单(很明显要排除掉 .next或你自定义的打包输出目录)

导出静态 HTML 页面

Examples

你可以将你的 Next.js应用当成一个不依赖于 Node.js服务的静态应用。此静态应用支持几乎所有的 Next.js特性,包括 异步导航、预获取、预加载和异步导入等。

使用

首先,Next.js的开发工作没什么变化,然后创建一个 Next.js的配置文件 config,就像下面这样:



// next.config.js
module.exports = {
  exportPathMap: function() {
    return {
      '/': { page: '/' },
      '/about': { page: '/about' },
      '/readme.md': { page: '/readme' },
      '/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } },
      '/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } },
      '/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } }
    }
  }
}

需要注意的是,如果声明的路径表示的是一个文件夹的话,那么最终将会导出一份类似于 /dir-name/index.html的文件,如果声明的路径是一个文件的话,那么最终将会以指定的文件名导出,例如上述代码中,就会导出一个 readme.md的文件。如果你使用了一个不是以 .html结尾的文件,那么在解析此文件的时候,你需要给 text/html设置一个 Content-Type头(header)

通过上述的类似代码,你可以指定你想要导出的静态页面。

接着,输入以下命令:



next build
next export

或许,你还可以在 package.json文件中多添加一条命令:




{
  "scripts": {
    "build": "next build && next export"
  }
}

现在就只需要输入这一条命令就行了:


npm run build

这样,你在 out目录下就有了一个当前应用的静态网站了。

你也可以自定义输出目录,更多帮助可以在命令行中输入 next export -h 获取

现在,你就可以把输出目录(例如/out)部署到静态文件服务器了,需要注意的是,如果你想要部署到 Github上的话,那么需要需要增加一个步骤

例如,只需要进入 out目录,然后输入以下命令,就可以把你的应用部署到 ZEIT now



now

局限性

当你输入 next export命令时,我们帮你构建了应用的 HTML静态版本,在此阶段,我们将会执行页面中的 getInitialProps函数。

所以,你只能使用 context对象传递给 getInitialPropspathnamequeryasPath 字段,而 reqres则是不可用的(resres只在服务器端可用)。

基于此,你也无法在我们预先构建 HTML文件的时候,动态的呈现 HTML页面,如果你真的想要这么做(指动态构建页面)的话,请使用 next start

相关技巧

FAQ

可用在生产环境使用吗? https://zeit.co 就是使用 `Next.js`构建的。

无论是开发者体验还是终端表现,它都超出预期,所以我们决定将它共享到社区中。

体积有多大?

客户端包的大小根据每个应用程序的功能等不同而不尽相同。 一个最简单的 Next程序包在 gzip压缩后可能只有 65kb 大小。

它(指Next.js) 和 `create-react-app`是差不多的吗?

是也不是。 说是,是因为二者都让你的开发变得更轻松。 说不是,则是因为 Next.js强制规定了一些目录结构,以便我们能实现更多高级的操作,例如:

  • 服务器端渲染(SSR)
  • 代码自动分割

此外,Next.js还内置了两个对于单页应用来说比较重要的特性:

  • Routing with lazy component loading: <Link> (by importing next/link)
  • 修改 <head>元素的方法(通过导入 next/head)

如果你想在 Next.js或其他 React应用中复用组件,则使用 create-react-app是一个很好的选择,你可以稍后将其导入以保证代码库的纯净。

如何使用嵌入式CSS(`CSS-in-JS`)方案?

Next.js自带的库 styled-jsx支持 局部(scoped)css,当然,你也可以在 Next应用中添加上面所提到的任何你喜欢的代码库来使用你想要的 CSS-in-JS解决方案。

如何使用类似于 SASS / SCSS / LESS 之类的 CSS 预处理器?

Next.js自带的库 styled-jsx支持 局部(scoped)css,当然,你也可以在 Next应用中使用以下示例中的任何一种 CSS预处理器方案:

What syntactic features are transpiled? How do I change them?

(语法特性)我们参照 V8引擎,因为 V8广泛支持 ES6async 以及 await,所以我们也就支持这些,因为 V8还不支持类装饰器(class decorator),所以我们也就不支持它(类装饰器)

可以看看 这些 以及 这些

Why a new Router?

Next.js is special in that:

  • Routes don’t need to be known ahead of time
  • Routes are always lazy-loadable
  • Top-level components can define getInitialProps that should block the loading of the route (either when server-rendering or lazy-loading)

基于上述几个特点,我们能够构造出一个具有以下两个功能的简单路由:

  • 每个*组件都会接收到一个 url对象来检查 url 或者 修改历史记录
  • <Link />组件作为类似于 <a/>等标签元素的容器以便进行客户端的页面切换。

我们已经在一些很有意思的场景下测试了路由的灵活性,更多相信可以看这里 nextgram

如何自定义路由?

Next.js提供了一个 request handler,利用其我们能够让任意 URL与 任何组件之间产生映射关系。 在客户端,<Link />组件有个 as属性,它能够改变获取到的 URL

如何获取数据?

这由你决定, getInitialProps 是一个 异步(async)函数(或者也可以说是一个返回 Promise的标准函数),它能够从任意位置获取数据。

能够配合使用 `GraphQL`吗

当然,这还有个用 Apollo 的例子呢。

能够配合使用 `Redux`吗?

当然,这也有个例子

为什么我不能在开发服务器中访问我的静态导出路由呢?

这是一个已知的 Next.js架构问题,在解决方案还没内置到框架中之前,你可以先看看这一个例子中的解决方法来集中管理你的路由。

我可以在 Next应用中使用我喜欢的 JavaScript库或工具包吗?

我们在发布第一版的时候就已经提供了很多例子,你可以看看这个目录

你们是怎么做出这个框架的?

我们力求达到的目标大部分都是从 由 Guillermo Rauch给出的[设计富Web应用的 7个原则]中受到启发,PHP的易用性也是一个很棒的灵感来源,我们觉得在很多你想使用 PHP来输出 HTML页面的情况下,Next.js都是一个很好的替代品,不过不像 PHP,我们从 ES6的模块化系统中获得好处,每个文件都能很轻松地导入一个能够用于延迟求值或测试的组件或函数。

当我们研究 React的服务器端渲染时,我们并没有做出太大的改变,因为我们偶然发现了 React作者 Jordan Walke写的 react-page (now deprecated)。

Contributing

Please see our contributing.md

Authors

原文发布时间为:2017年10月30日
原文作者:清夜
本文来源:掘金 如需转载请联系原作者

 

















上一篇:使用组策略限制设备使用


下一篇:Citrix VDI-in-a-Box第六篇:Tempalte创建篇