Next.js v4.1.4 文件中文翻譯

清夜發表於2017-10-30

最近想稍稍看下 ReactSSR框架 Next.js,因為不想看二手資料, 所以自己跑到 Github上看,Next.js的文件是英文的,看倒是大概也能看得懂, 但有些地方不太確定,而且英文看著畢竟不太爽你懂得,所以在網上搜了幾圈發現好像好像還沒有中文翻譯,想著長痛不如短痛, 索性一邊看一邊翻譯,自己翻譯的東西自己看得也爽,不過畢竟能力有限,有些地方我也不知道該怎麼翻譯才好,所以翻譯得不太通暢, 或者有幾句乾脆不翻譯了。

so,各位若是覺得我哪點翻譯得不太準確,或者對於那幾句我沒翻譯的地方有更好的見解,歡迎提出~

以下是全文翻譯的 Next.jsREADME.md檔案,版本是 v4.1.4,除了翻譯原文之外,還加了一點個人小小見解。

另外,沒太弄明白掘金寫文章的md頁面內超連結的語法是什麼,所以下面的目錄超連結沒有效果,不過不影響閱讀,想要更好的閱讀體驗可以去我的 github上看,別忘了 star哦~


screen shot 2016-10-25 at 2 37 27 pm

Next.js是一個用於React應用的極簡的服務端渲染框架。

請訪問 learnnextjs.com 以獲取更多詳細內容.


如何使用

安裝

安裝方法:

npm install --save next react react-dom
複製程式碼

Next.js 4 只支援 React 16.
由於 React 16React 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物件中將包括一個值為 truecancelled屬性。

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

相關文章