最近想稍稍看下 React
的 SSR
框架 Next.js,因為不想看二手資料,
所以自己跑到 Github上看,Next.js
的文件是英文的,看倒是大概也能看得懂,
但有些地方不太確定,而且英文看著畢竟不太爽你懂得,所以在網上搜了幾圈發現好像好像還沒有中文翻譯,想著長痛不如短痛,
索性一邊看一邊翻譯,自己翻譯的東西自己看得也爽,不過畢竟能力有限,有些地方我也不知道該怎麼翻譯才好,所以翻譯得不太通暢,
或者有幾句乾脆不翻譯了。
so,各位若是覺得我哪點翻譯得不太準確,或者對於那幾句我沒翻譯的地方有更好的見解,歡迎提出~
以下是全文翻譯的 Next.js的 README.md檔案,版本是 v4.1.4
,除了翻譯原文之外,還加了一點個人小小見解。
另外,沒太弄明白掘金寫文章的md
頁面內超連結的語法是什麼,所以下面的目錄超連結沒有效果,不過不影響閱讀,想要更好的閱讀體驗可以去我的 github上看,別忘了 star
哦~
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>
。
目前為止,我們已經介紹了:
- 自動編譯和打包 (使用
webpack
和babel
) - 程式碼熱更新
- 以
./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
譯者注:
scope CSS
的作用範圍,如果新增了jsx
屬性,則是不包括子元件的當前元件;如果新增了global
和jsx
屬性,則是包括了子元件在內的當前元件;如果沒新增任何屬性,則作用與 新增了global
和jsx
的作用類似,只不過next
不會對其進行額外的提取與優化打包scope CSS
的實現原理,其實就是在編譯好的程式碼的對應元素上,新增一個以jsx
開頭的類名(class
),然後將對應的樣式程式碼提取到此類名下
內聯式樣式 CSS-in-JS
幾乎可以使用所有的內聯樣式解決方案,最簡單一種如下:
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>
內宣告瞭其所有需要的內容,而不是假定這些東西已經在其他頁面中新增過了。
譯者注:
next
框架自帶<head>
標籤,作為當前頁面的<head>
,如果在元件內自定義了<Head>
,則自定義<Head>
內的元素(例如<title>
、<meta>
等)將會被追加到框架自帶的<head>
標籤中- 每個元件自定義的
<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
-URL
的path
部分query
-URL
的query 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>
來讓頁面在後臺同時獲取和預載入,以獲得最佳的頁面載入效能
客戶端路由行為與瀏覽器完全相同:
- 獲取元件
- 如果元件定義了
getInitialProps
,那麼進行資料的獲取,如果丟擲異常,則將渲染_error.js
- 在步驟
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
可以根據伺服器端路由的配置作出相應的 路由改變,例如,在伺服器端,你自定義規定當獲取/a
的path
請求的時候,返回一個位於/b
目錄下的頁面,則為了配合伺服器端的這種指定,你可以這麼定義<Link/>
元件:<Link href='/a' as='/b'><a>a</a></Link>
這種做法有一個好處,那就是儘管你將 /a請求指定到了 /b頁面,但是因為as的值為 /a,所以編譯後的 DOM元素顯示的連結的 href值為 /a,但是當真正點選連結時,響應的真正頁面還是 /b
譯者注2:
<Link>
元件主要用於路由跳轉功能,其可以接收一個必須的子元素(DOM
標籤或者純文字等)
- 如果新增的子元素是
DOM
元素,則Link
會為此子元素賦予路由跳轉功能;- 如果新增的元素是純文字,則
<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=Zeit
的 URL
字串,你也可以在此 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>
複製程式碼
命令式路由
你可以使用 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.push
和 props.url.replace
譯者注: 除非特殊需要,否則在元件內部不贊成(deprecated)使用
props.url.push
和props.url.replace
,而是建議使用next/router
的相關API
。
URL 物件
命令式路由 (next/router
)所接收的 URL
物件與 <Link>
的 URL
物件很類似,你可以使用相同的方式來push
和 replace
路由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 callRouter.push(url, as)
(or similar), then the value ofurl
will beas
. 上面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
}
複製程式碼
譯者注:
一般情況下,上述路由事件的發生順序如下:
routeChangeStart
beforeHistoryChange
routeChangeComplete
淺層路由
Examples
淺層路由(Shallow routing
)允許你在不觸發 getInitialProps
的情況下改變路由(URL
),你可以通過要載入頁面的 url
來獲取更新後的 pathname
和 query
,這樣就不會丟失路由狀態(state
)了。
你可以通過呼叫 Router.push
或 Router.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)
-path
是Next
應用當前的路由位置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 TC39
的dynamic 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>
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's
的renderStatic
等一些伺服器端渲染容器。
注意:<Main/>
之外的 React
元件都不會被瀏覽器初始化,如果你想在所有的頁面中使用某些元件(例如選單欄或者工具欄),首先保證不要在其中新增有關應用邏輯的內容,然後可以看看這個例子
譯者注: 上面那句話的意思是,在
_document.js
檔案中,你可以額外新增其他的一些元件,但是這所有的元件中,除了<Main/>
以外,其他的元件內的所有邏輯都不會被初始化和執行,這些不會被初始化和執行的邏輯程式碼包括除了render
之外的所有生命週期鉤子函式,例如componnetDidMount
、componentWillUpdate
,以及一些監聽函式,例如onClick
、onMouseOver
等,所以如果你要在_document.js
新增額外的元件,請確保這些元件中除了render
之外沒有其他的邏輯
自定義錯誤處理
客戶端和伺服器端都會捕獲並使用預設元件 error.js
來處理 404
和 500
錯誤。如果你希望自定義錯誤處理,可以對其進行覆寫:
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
物件傳遞給 getInitialProps
的 pathname
、query
和 asPath
欄位,而 req
或 res
則是不可用的(res
和 res
只在伺服器端可用)。
基於此,你也無法在我們預先構建
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 importingnext/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
廣泛支援 ES6
和 async
以及 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
- Arunoda Susiripala (@arunoda) – ▲ZEIT
- Tim Neutkens (@timneutkens)
- Naoyuki Kanezawa (@nkzawa) – ▲ZEIT
- Tony Kovanen (@tonykovanen) – ▲ZEIT
- Guillermo Rauch (@rauchg) – ▲ZEIT
- Dan Zajdband (@impronunciable) – Knight-Mozilla / Coral Project