Nextjs中文文檔

Next.js 是一個輕量級的 React 服務端渲染應用框架。javascript

Next.js中文站點 http://nextjs.frontendx.cn css

Next.js中文站Github https://github.com/raoenhui/next-site-cn html

當前翻譯版本爲 7.0.0-canary.8。前端


<!-- END doctoc generated TOC please keep comment here to allow auto update -->

怎麼使用

安裝

安裝它:

npm install --save next react react-dom

將下面腳本添加到 package.json 中:

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

下面, 文件系統是主要的 API. 每一個.js 文件將變成一個路由,自動處理和渲染。

新建 ./pages/index.js 到你的項目中:

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

運行 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 inside your project)
  • 靜態文件服務. ./static/ 映射到 /static/ (能夠 建立一個靜態目錄 在你的項目中)

這裏有個簡單的案例,能夠下載看看 sample app - nextgram

代碼自動分割

每一個頁面只會導入import中綁定以及被用到的代碼. 也就是說並不會加載不須要的代碼!

import cowsay from 'cowsay-browser'

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

CSS

支持嵌入樣式

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Basic css</li></ul>
</details></p>

咱們綁定 styled-jsx 來生成獨立做用域的 CSS. 目標是支持 "shadow CSS",可是 不支持獨立模塊做用域的 JS.

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查看.

內嵌樣式

<p><details>
<summary markdown="span">

<b>Examples</b>
</summary>

<ul><li>Styled components</li><li>Styletron</li><li>Glamor</li><li>Glamorous</li><li>Cxs</li><li>Aphrodite</li><li>Fela</li></ul>
</details></p>

有些狀況可使用 CSS 內嵌 JS 寫法。以下所示:

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

更復雜的內嵌樣式解決方案,特別是服務端渲染的時樣式更改。咱們能夠經過包裹自定義 Document,來添加樣式,案例以下:custom <Document>

使用 CSS / Sass / Less / Stylus files

支持用.css, .scss, .less or .styl,須要配置默認文件 next.config.js,具體可查看下面連接

靜態文件服務(如圖像)

在根目錄下新建文件夾叫static。代碼能夠經過/static/來引入相關的靜態資源。

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

_注意:不要自定義靜態文件夾的名字,只能叫static ,由於只有這個名字 Next.js 纔會把它看成靜態資源。

生成<head>

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/head-elements">Head elements</a></li>
<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/layout-component">Layout component</a></li>

</ul>
</details></p>

咱們設置一個內置組件來裝載<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>

咱們定義key屬性來避免重複的<head>標籤,保證<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" key="viewport" />
    </Head>
    <Head>
      <meta name="viewport" content="initial-scale=1.2, width=device-width" key="viewport" />
    </Head>
    <p>Hello world!</p>
  </div>
)

只有第二個<meta name="viewport" />才被渲染。

注意:在卸載組件時,<head>的內容將被清除。請確保每一個頁面都在其<head>定義了所須要的內容,而不是假設其餘頁面已經加過了

獲取數據以及組件生命週期

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Data fetch</li></ul>
</details></p>

若是你須要一個有狀態、生命週期或有初始數據的 React 組件(而不是上面的無狀態函數),以下所示:

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>
    )
  }
}

相信你注意到,當頁面渲染時加載數據,咱們使用了一個異步方法getInitialProps。它能異步獲取 JS 普通對象,並綁定在props

當服務渲染時,getInitialProps將會把數據序列化,就像JSON.stringify。因此確保getInitialProps返回的是一個普通 JS 對象,而不是Date, MapSet類型。

當頁面初次加載時,getInitialProps只會在服務端執行一次。getInitialProps只有在路由切換的時候(如Link組件跳轉或路由自定義跳轉)時,客戶端的纔會被執行。

當頁面初始化加載時,getInitialProps只會加載在服務端。只有當路由跳轉(Link組件跳轉或 API 方法跳轉)時,客戶端纔會執行getInitialProps

注意:getInitialProps將不能使用在子組件中。只能使用在pages頁面中。

<br/>

只有服務端用到的模塊放在 getInitialProps裏,請確保正確的導入了它們,可參考 import them properly
不然會拖慢你的應用速度。

<br/>

你也能夠給無狀態組件定義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 - URL 的 path 部分
  • query - URL 的 query 部分,並被解析成對象
  • asPath - 顯示在瀏覽器中的實際路徑(包含查詢部分),爲String類型
  • req - HTTP 請求對象 (只有服務器端有)
  • res - HTTP 返回對象 (只有服務器端有)
  • jsonPageRes - 獲取數據響應對象 (只有客戶端有)
  • err - 渲染過程當中的任何錯誤

路由

<Link>用法

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/hello-world">Hello World</a></li>

</ul>
</details></p>

能夠用 <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執行,新組件被渲染。

若是須要注入pathname, queryasPath到你組件中,你可使用withRouter

URL 對象

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/with-url-object-routing">With URL Object Routing</a></li>

</ul>
</details></p>

組件<Link>接收 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>

將生成 URL 字符串/about?name=Zeit,你可使用任何在Node.js URL module documentation定義過的屬性。

替換路由

<Link>組件默認將新 url 推入路由棧中。你可使用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>支持每一個組件所支持的onClick事件。若是你不提供<a>標籤,只會處理onClick事件而href將不起做用。

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

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

暴露 href 給子元素

如子元素是一個沒有 href 屬性的<a>標籤,咱們將會指定它以避免用戶重複操做。然而有些時候,咱們須要裏面有<a>標籤,可是Link組件不會被識別成超連接,結果不能將href傳遞給子元素。在這種場景下,你能夠定義一個Link組件中的布爾屬性passHref,強制將href傳遞給子元素。

注意: 使用a以外的標籤並且沒有經過passHref的連接可能會使導航看上去正確,可是當搜索引擎爬行檢測時,將不會識別成連接(因爲缺少 href 屬性),這會對你網站的 SEO 產生負面影響。

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>

禁止滾動到頁面頂部

<Link>的默認行爲就是滾到頁面頂部。當有 hash 定義時(#),頁面將會滾動到對應的 id 上,就像<a>標籤同樣。爲了預防滾動到頂部,能夠給<Link>
scroll={false}屬性:

<Link scroll={false} href="/?counter=10"><a>Disables scrolling</a></Link>
<Link href="/?counter=10"><a>Changes with scrolling to top</a></Link>

命令式

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/using-router">Basic routing</a></li>
<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/with-loading">With a page loading indicator</a></li>

</ul>
</details></p>

你也能夠用next/router實現客戶端路由切換

import Router from 'next/router'

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

攔截器 popstate

有些狀況(好比使用custom router),你可能想監聽popstate,在路由跳轉前作一些動做。
好比,你能夠操做 request 或強制 SSR 刷新

import Router from 'next/router'

Router.beforePopState(({ url, as, options }) => {
  // I only want to allow these two routes!
  if (as !== "/" || as !== "/other") {
    // Have SSR render bad routes as a 404.
    window.location.href = as
    return false
  }

  return true
});

若是你在beforePopState中返回 false,Router將不會執行popstate事件。
例如Disabling File-System Routing

以上Router對象的 API 以下:

  • 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
  • beforePopState(cb=function) - 在路由器處理事件以前攔截.

pushreplace 函數的第二個參數as,是爲了裝飾 URL 做用。若是你在服務器端設置了自定義路由將會起做用。

URL 對象用法

pushreplace可接收的 URL 對象(<Link>組件的 URL 對象同樣)來生成 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>

也能夠像<Link>組件同樣添加額外的參數。

路由事件

你能夠監聽路由相關事件。
下面是事件支持列表:

  • routeChangeStart(url) - 路由開始切換時觸發
  • routeChangeComplete(url) - 完成路由切換時觸發
  • routeChangeError(err, url) - 路由切換報錯時觸發
  • beforeHistoryChange(url) - 瀏覽器 history 模式開始切換時觸發
  • hashChangeStart(url) - 開始切換 hash 值可是沒有切換頁面路由時觸發
  • hashChangeComplete(url) - 完成切換 hash 值可是沒有切換頁面路由時觸發
這裏的 url是指顯示在瀏覽器中的 url。若是你用了 Router.push(url, as)(或相似的方法),那瀏覽器中的 url 將會顯示 as 的值。

下面是如何正確使用路由事件routeChangeStart的例子:

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

Router.events.on('routeChangeStart', handleRouteChange)

若是你不想長期監聽該事件,你能夠用off事件去取消監聽:

Router.events.off('routeChangeStart', handleRouteChange)

若是路由加載被取消(好比快速連續雙擊連接)

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

淺層路由

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/with-shallow-routing">Shallow Routing</a></li>

</ul>
</details></p>

淺層路由容許你改變 URL 可是不執行getInitialProps生命週期。你能夠加載相同頁面的 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.router.query你將會看到更新的 URL。

你能夠在componentdidupdate鉤子函數中監聽 URL 的變化。

componentDidUpdate(prevProps) {
  const { pathname, query } = this.props.router
  // verify props have changed to avoid an infinite loop
  if (query.id !== prevProps.router.query.id) {
    // fetch data based on the new query
  }
}

注意:

淺層路由只做用於相同 URL 的參數改變,好比咱們假定有個其餘路由about,而你向下面代碼樣運行:

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

那麼這將會出現新頁面,即便咱們加了淺層路由,可是它仍是會卸載當前頁,會加載新的頁面並觸發新頁面的getInitialProps

高階組件

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/using-with-router">Using the `withRouter` utility</a></li>

</ul>
</details></p>

若是你想應用裏每一個組件都處理路由對象,你可使用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)

上面路由對象的 API 能夠參考next/router.

預加載頁面

⚠️ 只有生產環境纔有此功能 ⚠️

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Prefetching</li></ul>
</details></p>

Next.js 有容許你預加載頁面的 API。

用 Next.js 服務端渲染你的頁面,能夠達到全部你應用裏全部將來會跳轉的路徑即時響應,有效的應用 Next.js,能夠經過預加載應用程序的功能,最大程度的初始化網站性能。查看更多.

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>

命令式 prefetch 寫法

大多數預加載是經過<Link />處理的,可是咱們還提供了命令式 API 用於更復雜的場景。

import { withRouter } from 'next/router'

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

路由實例只容許在應用程序的客戶端。以防服務端渲染髮生錯誤,建議 prefetch 事件寫在componentDidMount()生命週期裏。

import React from 'react'
import { withRouter } from 'next/router'

class MyLink extends React.Component {
  componentDidMount() {
    const { router } = this.props
    router.prefetch('/dynamic')
  }
  
  render() {
    const { router } = this.props
    return (
       <div>
        <a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
          A route transition will happen after 100ms
        </a>
      </div>   
    )
  }
}

export default withRouter(MyLink)

自定義服務端路由

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/custom-server">Basic custom server</a></li>
<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/custom-server-express">Express integration</a></li>
<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/custom-server-hapi">Hapi integration</a></li>
<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/custom-server-koa">Koa integration</a></li>
<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/parameterized-routing">Parameterized routing</a></li>
<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/ssr-caching">SSR caching</a></li>

</ul>
</details></p>

通常你使用next start命令來啓動 next 服務,你還能夠編寫代碼來自定義路由,如使用路由正則等。

當使用自定義服務文件,以下面例子所示叫 server.js 時,確保你更新了 package.json 中的腳本。

{
  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  }
}

下面這個例子使 /a 路由解析爲./pages/b,以及/b 路由解析爲./pages/a;

// This file doesn't 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(opts: object)

opts 的屬性以下:

  • dev (boolean) 判斷 Next.js 應用是否在開發環境 - 默認false
  • dir (string) Next 項目路徑 - 默認'.'
  • quiet (boolean) 是否隱藏包含服務端消息在內的錯誤信息 - 默認false
  • conf (object) 與next.config.js的對象相同 - 默認{}

生產環境的話,能夠更改 package.json 裏的start腳本爲NODE_ENV=production node server.js

禁止文件路由

默認狀況,Next將會把/pages下的全部文件匹配路由(如/pages/some-file.js 渲染爲 site.com/some-file

若是你的項目使用自定義路由,那麼有可能不一樣的路由會獲得相同的內容,能夠優化 SEO 和用戶體驗。

禁止路由連接到/pages下的文件,只需設置next.config.js文件以下所示:

// next.config.js
module.exports = {
  useFileSystemPublicRoutes: false
}

注意useFileSystemPublicRoutes只禁止服務端的文件路由;可是客戶端的仍是禁止不了。

你若是想配置客戶端路由不能跳轉文件路由,能夠參考Intercepting popstate

動態前綴

有時你須要設置動態前綴,能夠在請求時設置assetPrefix改變前綴。

使用方法以下:

const next = require('next')
const micro = require('micro')

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

app.prepare().then(() => {
  const server = micro((req, res) => {
    // Add assetPrefix support based on the hostname
    if (req.headers.host === 'my-app.com') {
      app.setAssetPrefix('http://cdn.com/myapp')
    } else {
      app.setAssetPrefix('')
    }

    handleNextRequests(req, res)
  })

  server.listen(port, (err) => {
    if (err) {
      throw err
    }

    console.log(`> Ready on http://localhost:${port}`)
  })
})

動態導入

<p><details>
<summary markdown="span">Examples</summary>
<ul>

<li><a href="https://github.com/zeit/next.js/tree/7.0.0-canary.11/examples/with-dynamic-import">With Dynamic Import</a></li>

</ul>
</details></p>

ext.js 支持 JavaScript 的 TC39 提議dynamic import proposal。你能夠動態導入 JavaScript 模塊(如 React 組件)。

動態導入至關於把代碼分紅各個塊管理。Next.js 服務端動態導入功能,你能夠作不少炫酷事情。

下面介紹一些動態導入方式:

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: () => {
    const components = {
      Hello1: import('../components/hello1'),
      Hello2: import('../components/hello2')
    }

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

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

自定義 <App>

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Using _app.js for layout</li></ul>
<ul><li>Using _app.js to override componentDidCatch</li></ul>
</details></p>

組件來初始化頁面。你能夠重寫它來控制頁面初始化,以下面的事:

  • 當頁面變化時保持頁面佈局
  • 當路由變化時保持頁面狀態
  • 使用componentDidCatch自定義處理錯誤
  • 注入額外數據到頁面裏 (如 GraphQL 查詢)

重寫的話,新建./pages/_app.js文件,重寫 App 模塊以下所示:

import App, {Container} from 'next/app'
import React from 'react'

export default class MyApp extends App {
  static async getInitialProps ({ Component, router, ctx }) {
    let pageProps = {}

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }

    return {pageProps}
  }

  render () {
    const {Component, pageProps} = this.props
    return <Container>
      <Component {...pageProps} />
    </Container>
  }
}

自定義 <Document>

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Styled components custom document</li></ul>
<ul><li>Google AMP</li></ul>
</details></p>

  • 在服務端呈現
  • 初始化服務端時添加文檔標記元素
  • 一般實現服務端渲染會使用一些 css-in-js 庫,如styled-components, glamorousemotionstyled-jsx是 Next.js 自帶默認使用的 css-in-js 庫

Next.js會自動定義文檔標記,好比,你歷來不須要添加<html>, <body>等。若是想自定義文檔標記,你能夠新建./pages/_document.js,而後擴展Document類:

// _document is only rendered on the server side and not on the client side
// Event handlers like onClick can't be added to this file

// ./pages/_document.js
import Document, { Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

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

鉤子getInitialProps接收到的參數ctx對象都是同樣的

  • 回調函數renderPage是會執行 React 渲染邏輯的函數(同步),這種作法有助於此函數支持一些相似於 Aphrodite 的 renderStatic 等一些服務器端渲染容器。

注意:<Main />外的 React 組件將不會渲染到瀏覽器中,因此那添加應用邏輯代碼。若是你頁面須要公共組件(菜單或工具欄),能夠參照上面說的App組件代替。

自定義錯誤處理

404和500錯誤客戶端和服務端都會經過error.js組件處理。若是你想改寫它,則新建_error.js在文件夾中:

import React from 'react'

export default class Error extends React.Component {
  static getInitialProps({ res, err }) {
    const statusCode = res ? res.statusCode : err ? err.statusCode : 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-unfetch'

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>
    )
  }
}
若是你自定義了個錯誤頁面,你能夠引入本身的錯誤頁面來代替 next/error

自定義配置

若是你想自定義 Next.js 的高級配置,能夠在根目錄下新建next.config.js文件(與pages/package.json一塊兒)

注意:next.config.js是一個 Node.js 模塊,不是一個 JSON 文件,能夠用於 Next 啓動服務已經構建階段,可是不做用於瀏覽器端。

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

或使用一個函數:

module.exports = (phase, {defaultConfig}) => {
  //
  // https://github.com/zeit/
  return {
    /* config options here */
  }
}

phase是配置文件被加載時的當前內容。你可看到全部的 phases 常量:constants
這些常量能夠經過next/constants引入:

const {PHASE_DEVELOPMENT_SERVER} = require('next/constants')
module.exports = (phase, {defaultConfig}) => {
  if(phase === PHASE_DEVELOPMENT_SERVER) {
    return {
      /* development only config options here */
    }
  }

  return {
    /* config options for all phases except development here */
  }
}

設置自定義構建目錄

你能夠自定義一個構建目錄,如新建build文件夾來代替.next 文件夾成爲構建目錄。若是沒有配置構建目錄,構建時將會自動新建.next文件夾

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

禁止 etag 生成

你能夠禁止 etag 生成根據你的緩存策略。若是沒有配置,Next 將會生成 etags 到每一個頁面中。

// next.config.js
module.exports = {
  generateEtags: false
}

配置 onDemandEntries

Next 暴露一些選項來給你控制服務器部署以及緩存頁面:

module.exports = {
  onDemandEntries: {
    // period (in ms) where the server will keep pages in the buffer
    maxInactiveAge: 25 * 1000,
    // number of pages that should be kept simultaneously without being disposed
    pagesBufferLength: 2,
  }
}

這個只是在開發環境纔有的功能。若是你在生成環境中想緩存 SSR 頁面,請查看SSR-caching

配置頁面後綴名解析擴展

如 typescript 模塊@zeit/next-typescript,須要支持解析後綴名爲.ts的文件。pageExtensions 容許你擴展後綴名來解析各類 pages 下的文件。

// next.config.js
module.exports = {
  pageExtensions: ['jsx', 'js']
}

配置構建 ID

Next.js 使用構建時生成的常量來標識你的應用服務是哪一個版本。在每臺服務器上運行構建命令時,可能會致使多服務器部署出現問題。爲了保持同一個構建 ID,能夠配置generateBuildId函數:

// next.config.js
module.exports = {
  generateBuildId: async () => {
    // For example get the latest git commit hash here
    return 'my-build-id'
  }
}

自定義 webpack 配置

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Custom webpack bundle analyzer</li></ul>
</details></p>

可使用些一些常見的模塊

注意: webpack方法將被執行兩次,一次在服務端一次在客戶端。你能夠用isServer屬性區分客戶端和服務端來配置

多配置能夠組合在一塊兒,如:

const withTypescript = require('@zeit/next-typescript')
const withSass = require('@zeit/next-sass')

module.exports = withTypescript(withSass({
  webpack(config, options) {
    // Further custom configuration here
    return config
  }
}))

爲了擴展webpack使用,能夠在next.config.js定義函數。

// next.config.js is not transformed by Babel. So you can only use javascript features supported by your version of Node.js.

module.exports = {
  webpack: (config, { buildId, dev, isServer, defaultLoaders }) => {
    // 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的第二個參數是個對象,你能夠自定義配置它,對象屬性以下所示:

  • buildId - 字符串類型,構建的惟一標示
  • dev - Boolean型,判斷你是否在開發環境下
  • isServer - Boolean 型,爲true使用在服務端, 爲false使用在客戶端.
  • defaultLoaders - 對象型 ,內部加載器, 你能夠以下配置

    • babel - 對象型,配置babel-loader.
    • hotSelfAccept - 對象型, hot-self-accept-loader配置選項.這個加載器只能用於高階案例。如 @zeit/next-typescript添加頂層 typescript 頁面。

defaultLoaders.babel使用案例以下:

// Example next.config.js for adding a loader that depends on babel-loader
// This source was taken from the @zeit/next-mdx plugin source: 
// https://github.com/zeit/next-plugins/blob/master/packages/next-mdx
module.exports = {
  webpack: (config, {}) => {
    config.module.rules.push({
      test: /\.mdx/,
      use: [
        options.defaultLoaders.babel,
        {
          loader: '@mdx-js/loader',
          options: pluginOptions.options
        }
      ]
    })

    return config
  }
}

自定義 babel 配置

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Custom babel configuration</li></ul>
</details></p>

爲了擴展方便咱們使用babel,能夠在應用根目錄新建.babelrc文件,該文件可配置。

若是有該文件,咱們將會考慮數據源,所以也須要定義 next 項目須要的東西,也就是 next/babel預設。

這種設計方案將會使你不詫異於咱們能夠定製 babel 配置。

下面是.babelrc文件案例:

{
  "presets": ["next/babel"],
  "plugins": []
}

next/babel預設可處理各類 React 應用所須要的狀況。包括:

  • preset-env
  • preset-react
  • plugin-proposal-class-properties
  • plugin-proposal-object-rest-spread
  • plugin-transform-runtime
  • styled-jsx

presets / plugins 不容許添加到.babelrc中,然而你能夠配置next/babel預設:

{
  "presets": [
    ["next/babel", {
      "preset-env": {},
      "transform-runtime": {},
      "styled-jsx": {},
      "class-properties": {}
    }]
  ],
  "plugins": []
}

"preset-env"模塊選項應該保持爲 false,不然 webpack 代碼分割將被禁用。

暴露配置到服務端和客戶端

next/config模塊使你應用運行時能夠讀取些存儲在next.config.js的配置項。serverRuntimeConfig屬性只在服務器端可用,publicRuntimeConfig屬性在服務端和客戶端可用。

// next.config.js
module.exports = {
  serverRuntimeConfig: { // Will only be available on the server side
    mySecret: 'secret'
  },
  publicRuntimeConfig: { // Will be available on both server and client
    staticFolder: '/static',
    mySecret: process.env.MY_SECRET // Pass through env variables
  }
}
// pages/index.js
import getConfig from 'next/config'
// Only holds serverRuntimeConfig and publicRuntimeConfig from next.config.js nothing else.
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

export default () => <div>
  <img src={`${publicRuntimeConfig.staticFolder}/logo.png`} alt="logo" />
</div>

啓動服務選擇 hostname

啓動開發環境服務能夠設置不一樣的 hostname,你能夠在啓動命令後面加上--hostname 主機名-H 主機名。它將會啓動一個 TCP 服務器來監聽鏈接所提供的主機。

CDN 支持前綴

創建一個 CDN,你能配置assetPrefix選項,去配置你的 CDN 源。

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 運行時將會自動添加前綴,可是對於/static是沒有效果的,若是你想這些靜態資源也能使用 CDN,你須要本身添加前綴。有一個方法能夠判斷你的環境來加前綴,如 in this example

項目部署

部署中,你能夠先構建打包生成環境代碼,再啓動服務。所以,構建和啓動分爲下面兩條命令:

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 也有其餘託管解決方案。請查考 wiki 章節'Deployment'

注意:NODE_ENV能夠經過next命令配置,若是沒有配置,會最大渲染,若是你使用編程式寫法的話programmatically,你須要手動設置NODE_ENV=production

注意:推薦將.next或自定義打包文件夾custom dist folder放入.gitignore.npmignore中。不然,使用filesnow.files
添加部署白名單,並排除.next或自定義打包文件夾。

瀏覽器支持

Next.js 支持 IE11 和全部的現代瀏覽器使用了@babel/preset-env。爲了支持 IE11,Next.js 須要全局添加Promise的 polyfill。有時你的代碼或引入的其餘 NPM 包的部分功能現代瀏覽器不支持,則須要用 polyfills 去實現。

ployflls 實現案例爲polyfills

導出靜態頁面

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>Static export</li></ul>
</details></p>

next export能夠輸出一個 Next.js 應用做爲靜態資源應用而不依靠 Node.js 服務。
這個輸出的應用幾乎支持 Next.js 的全部功能,包括動態路由,預獲取,預加載以及動態導入。

next export將把全部有可能渲染出的 HTML 都生成。這是基於映射對象的pathname關鍵字關聯到頁面對象。這個映射叫作exportPathMap

頁面對象有2個屬性:

  • page - 字符串類型,頁面生成目錄
  • query - 對象類型,當預渲染時,query對象將會傳入頁面的生命週期getInitialProps中。默認爲{}

使用

一般開發 Next.js 應用你將會運行:

next build
next export

next export命令默認不須要任何配置,將會自動生成默認exportPathMap生成pages目錄下的路由你頁面。

若是你想動態配置路由,能夠在next.config.js中添加異步函數exportPathMap

// next.config.js
module.exports = {
  exportPathMap: async function (defaultPathMap) {
    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' } }
    }
  }
}
注意:若是 path 的結尾是目錄名,則將導出 /dir-name/index.html,可是若是結尾有擴展名,將會導出對應的文件,如上 /readme.md。若是你使用 .html之外的擴展名解析文件時,你須要設置 header 的 Content-Type頭爲"text/html".

輸入下面命令:

next build
next export

你能夠在package.json添加一個 NPM 腳本,以下所示:

{
  "scripts": {
    "build": "next build",
    "export": "npm run build && next export"
  }
}

接着只用執行一次下面命令:

npm run export

而後你將會有一個靜態頁面應用在out 目錄下。

你也能夠自定義輸出目錄。能夠運行 next export -h命令查看幫助。

如今你能夠部署out目錄到任意靜態資源服務器上。注意若是部署 GitHub Pages 須要加個額外的步驟,文檔以下

例如,訪問out目錄並用下面命令部署應用ZEIT Now.

now

限制

使用next export,咱們建立了個靜態 HTML 應用。構建時將會運行頁面裏生命週期getInitialProps 函數。

reqres只在服務端可用,不能經過getInitialProps

因此你不能預構建 HTML 文件時動態渲染 HTML 頁面。若是你想動態渲染能夠運行 next start或其餘自定義服務端 API。

多 zone

<p><details>
<summary markdown="span">Examples</summary>
<ul><li>With Zones</li></ul>
</details></p>

一個 zone 時一個單獨的 Next.js 應用。若是你有不少 zone,你能夠合併成一個應用。

例如,你以下有兩個 zone:

有多 zone 應用技術支持,你能夠將幾個應用合併到一個,並且能夠自定義 URL 路徑,使你能同時單獨開發各個應用。

與 microservices 觀念相似, 只是應用於前端應用.

怎麼定義一個 zone

zone 沒有單獨的 API 文檔。你須要作下面事便可:

怎麼合併他們

你能使用 HTTP 代理合並 zone

你能使用代理micro proxy來做爲你的本地代理服務。它容許你定義路由規則以下:

{
  "rules": [
    {"pathname": "/docs**", "method":["GET", "POST", "OPTIONS"], "dest": "https://docs.my-app.com"},
    {"pathname": "/**", "dest": "https://ui.my-app.com"}
  ]
}

生產環境部署,若是你使用了ZEIT now,能夠它的使用path alias 功能。不然,你能夠設置你已使用的代理服務編寫上面規則來路由 HTML 頁面

技巧

問答

<details>
<summary markdown="span">這個產品能夠用於生產環境嗎?</summary>
<div markdown="span">
https://zeit.co 都是一直用 Next.js 寫的。

它的開發體驗和終端用戶體驗都很好,因此咱們決定開源出來給你們共享。
</div>
</details>

<details>
<summary markdown="span">體積多大?</summary>
<div markdown="span">
客戶端大小根據應用需求不同大小也不同。

一個最簡單 Next 應該用 gzip 壓縮後大約65kb
</div>
</details>

<details >
<summary markdown="span">這個像 create-react-app?</summary>
<div markdown="span">
是或不是.

是,由於它讓你的 SSR 開發更簡單。

不是,由於它規定了必定的目錄結構,使咱們能作如下更高級的事:

  • 服務端渲染
  • 自動代碼分割

此外,Next.js 還提供兩個內置特性:

  • 路由與懶加載組件: <Link> (經過引入 next/link)
  • 修改<head>的組件: <Head> (經過引入 next/head)

若是你想寫共用組件,能夠嵌入 Next.js 應用和 React 應用中,推薦使用create-react-app。你能夠更改import保持代碼清晰。
</div>
</details>

<details>
<summary markdown="span">怎麼解決 CSS 嵌入 JS 問題?</summary>
<div markdown="span">
Next.js 自帶styled-jsx庫支持 CSS 嵌入 JS。並且你能夠選擇其餘嵌入方法到你的項目中,可參考文檔as mentioned before
</div>
</details>

<details>
<summary markdown="span">哪些語法會被轉換?怎麼轉換它們?</summary>
<div markdown="span">
咱們遵循 V8 引擎的,現在 V8 引擎普遍支持 ES6 語法以及asyncawait語法,因此咱們支持轉換它們。可是 V8 引擎不支持修飾器語法,因此咱們也不支持轉換這語法。

能夠參照這些 以及 這些
</div>
</details>

<details>
<summary markdown="span">爲何使用新路由?</summary>
<div markdown="span">
Next.js 的特別之處以下所示:

  • 路由不須要被提早知道
  • 路由老是被懶加載
  • 頂層組件能夠定義生命週期getInitialProps來阻止路由加載(當服務端渲染或路由懶加載時)

所以,咱們能夠介紹一個很是簡單的路由方法,它由下面兩部分組成:

  • 每一個頂層組件都將會收到一個url對象,來檢查 url 或修改歷史記錄
  • <Link />組件用於包裝如(<a/>)標籤的元素容器,來執行客戶端轉換。

咱們使用了些有趣的場景來測試路由的靈活性,例如,可查看nextgram
</div>
</details>

<details>
<summary markdown="span">我怎麼定義自定義路由?</summary>
<div markdown="span">
咱們經過請求處理來添加任意 URL 與任意組件以前的映射關係。

在客戶端,咱們<Link>組件有個屬性as,能夠裝飾改變獲取到的 URL。
</div>
</details>

<details>
<summary markdown="span">怎麼獲取數據?</summary>
<div markdown="span">
這由你決定。getInitialProps是一個異步函數async(也就是函數將會返回個Promise)。你能夠在任意位置獲取數據。
</div>
</details>

<details>
<summary markdown="span">我可使用 GraphQL 嗎?</summary>
<div markdown="span">
是的! 這裏有個例子Apollo.
</div>
</details>

<details>
<summary markdown="span">我可使用 Redux 嗎?</summary>
<div markdown="span">
是的! 這裏有個例子
</div>
</details>

<details>
<summary markdown="span">我能夠在 Next 應用中使用我喜歡的 Javascript 庫或工具包嗎?</summary>
<div markdown="span">
從咱們第一次發版就已經提供不少例子,你能夠查看這些例子
</div>
</details>

<details>
<summary markdown="span">什麼啓發咱們作這個?</summary>
<div markdown="span">
咱們實現的大部分目標都是經過 Guillermo Rauch 的Web 應用的7原則來啓發出的。

PHP 的易用性也是個很好的靈感來源,咱們以爲 Next.js 能夠替代不少須要用 PHP 輸出 HTML 的場景。

與 PHP 不一樣的是,咱們得利於 ES6 模塊系統,每一個文件會輸出一個組件或方法,以即可以輕鬆的導入用於懶加載和測試

咱們研究 React 的服務器渲染時並無花費很大的步驟,由於咱們發現一個相似於 Next.js 的產品,React 做者 Jordan Walke 寫的react-page (如今已經廢棄)
</div>
</details>

貢獻

可查看 contributing.md

做者

相關文章
相關標籤/搜索