最近想稍稍看下 React
的 SSR
框架 Next.js,由於不想看二手資料, 因此本身跑到 Github上看,Next.js
的文檔是英文的,看卻是大概也能看得懂, 但有些地方不太肯定,並且英文看着畢竟不太爽你懂得,因此在網上搜了幾圈發現好像好像尚未中文翻譯,想着長痛不如短痛, 索性一邊看一邊翻譯,本身翻譯的東西本身看得也爽,不過畢竟能力有限,有些地方我也不知道該怎麼翻譯纔好,因此翻譯得不太通暢, 或者有幾句乾脆不翻譯了。css
so,各位如果以爲我哪點翻譯得不太準確,或者對於那幾句我沒翻譯的地方有更好的看法,歡迎提出~html
如下是全文翻譯的 Next.js的 README.md文件,版本是 v4.1.4
,除了翻譯原文以外,還加了一點我的小小看法。node
另外,沒太弄明白掘金寫文章的md
頁面內超連接的語法是什麼,因此下面的目錄超連接沒有效果,不過不影響閱讀,想要更好的閱讀體驗能夠去個人 github上看,別忘了 star
哦~react
Next.js是一個用於React應用的極簡的服務端渲染框架。webpack
請訪問 learnnextjs.com 以獲取更多詳細內容.git
安裝方法:github
npm install --save next react react-dom
複製代碼
Next.js 4
只支持 React 16.
因爲React 16
和React 15
的工做方式以及使用方法不盡相同,因此咱們不得不移除了對React 15
的支持web
在你的 package.json
文件中添加以下代碼:express
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
複製代碼
接下來,大部分事情都交由文件系統來處理。每一個 .js
文件都變成了一個自動處理和渲染的路由。npm
在項目中新建 ./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>
複製代碼
咱們提供 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
),而後將對應的樣式代碼提取到此類名下
幾乎可使用全部的內聯樣式解決方案,最簡單一種以下:
export default () => <p style={{ color: 'red' }}>hi there</p>
複製代碼
爲了使用更多複雜的 CSS-in-JS
內聯樣式方案,你可能不得不在服務器端渲染的時候強制樣式刷新。咱們經過容許自定義包裹着每一個頁面的 <Document>
組件的方式來解決此問題。
在你的項目的根目錄新建 static
文件夾,而後你就能夠在你的代碼經過 /static/
開頭的路徑來引用此文件夾下的文件:
export default () => <img src="/static/my-image.png" /> 複製代碼
<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" /> </Head> <p>Hello world!</p> </div> 複製代碼
注意:當組件卸載的時候,組件內定義的 <Head>
將會被清空,因此請確保每一個頁面都在其各自的 <Head>
內聲明瞭其全部須要的內容,而不是假定這些東西已經在其餘頁面中添加過了。
譯者注:
next
框架自帶<head>
標籤,做爲當前頁面的<head>
,若是在組件內自定義了<Head>
,則自定義<Head>
內的元素(例如<title>
、<meta>
等)將會被追加到框架自帶的<head>
標籤中- 每一個組件自定義的
<Head>
內容只會應用在各自的頁面上,子組件內定義的<Head>
也會追加到當前頁面的<head>
內,若是有重複定義的標籤或屬性,則子組件覆蓋父組件,位於文檔更後面的組件覆蓋更前面的組件。
你能夠經過導出一個基於 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>
能夠經過 <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)。
<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
。
命令式路由 (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
淺層路由(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
方法
若是你想在應用的任何組件都能獲取到 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
。
(下面就是一個小例子)
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>
複製代碼
通常來講,你可使用 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
。
Next.js
支持 JavaScript TC39
的dynamic import proposal規範,因此你能夠動態導入(import
) JavaScript
模塊(例如 React Component
)。
你能夠將動態導入理解爲一種將代碼分割爲更易管理和理解的方式。 因爲 Next.js
支持服務器端渲染側(SSR
)的動態導入,因此你能夠用它來作一些炫酷的東西。
SSR
)import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(import('../components/hello'))
export default () =>
<div> <Header /> <DynamicComponent /> <p>HOME PAGE is here!</p> </div>
複製代碼
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>
複製代碼
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>
複製代碼
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'
}
複製代碼
Next
暴露了一些可以讓你本身控制如何部署服務或者緩存頁面的配置:
module.exports = {
onDemandEntries: {
// 控制頁面在內存`buffer`中緩存的時間,單位是 ms
maxInactiveAge: 25 * 1000,
// number of pages that should be kept simultaneously without being disposed
pagesBufferLength: 2,
}
}
複製代碼
你能夠經過 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
的使用,你能夠在應用的根目錄下新建 .babelrc
文件,此文件是非必須的。 若是此文件存在,那麼咱們就認爲這個纔是真正的Babel
配置文件,所以也就須要爲其定義一些 next
項目須要的東西, 並將之當作是next/babel
的預設配置(preset
) 這種設計是爲了不你有可能對咱們可以定製 babel
配置而感到詫異。
下面是一個 .babelrc
文件的示例:
{
"presets": ["next/babel", "stage-0"]
}
複製代碼
你能夠設定 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
或你自定義的打包輸出目錄)
你能夠將你的 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
不管是開發者體驗仍是終端表現,它都超出預期,因此咱們決定將它共享到社區中。
客戶端包的大小根據每一個應用程序的功能等不一樣而不盡相同。 一個最簡單的 Next
程序包在 gzip
壓縮後可能只有 65kb 大小。
是也不是。 說是,是由於兩者都讓你的開發變得更輕鬆。 說不是,則是由於 Next.js
強制規定了一些目錄結構,以便咱們能實現更多高級的操做,例如:
SSR
)此外,Next.js
還內置了兩個對於單頁應用來講比較重要的特性:
<Link>
(by importing next/link
)<head>
元素的方法(經過導入 next/head
)若是你想在 Next.js
或其餘 React
應用中複用組件,則使用 create-react-app
是一個很好的選擇,你能夠稍後將其導入以保證代碼庫的純淨。
Next.js
自帶的庫 styled-jsx支持 局部(scoped
)css
,固然,你也能夠在 Next
應用中添加上面所提到的任何你喜歡的代碼庫來使用你想要的 CSS-in-JS
解決方案。
Next.js
自帶的庫 styled-jsx支持 局部(scoped
)css
,固然,你也能夠在 Next
應用中使用如下示例中的任何一種 CSS
預處理器方案:
(語法特性)咱們參照 V8
引擎,由於 V8
普遍支持 ES6
和 async
以及 await
,因此咱們也就支持這些,由於 V8
還不支持類裝飾器(class decorator
),因此咱們也就不支持它(類裝飾器)
Next.js is special in that:
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
的標準函數),它可以從任意位置獲取數據。
固然,這還有個用 Apollo 的例子呢。
固然,這也有個例子。
這是一個已知的 Next.js
架構問題,在解決方案還沒內置到框架中以前,你能夠先看看這一個例子中的解決方法來集中管理你的路由。
咱們在發佈初版的時候就已經提供了不少例子,你能夠看看這個目錄
咱們力求達到的目標大部分都是從 由 Guillermo Rauch
給出的[設計富Web應用的 7個原則]中受到啓發,PHP
的易用性也是一個很棒的靈感來源,咱們以爲在不少你想使用 PHP
來輸出 HTML
頁面的狀況下,Next.js
都是一個很好的替代品,不過不像 PHP
,咱們從 ES6
的模塊化系統中得到好處,每一個文件都能很輕鬆地導入一個可以用於延遲求值或測試的組件或函數。
當咱們研究 React
的服務器端渲染時,咱們並無作出太大的改變,由於咱們偶然發現了 React
做者 Jordan Walke
寫的 react-page (now deprecated)。
Please see our contributing.md