安裝它:
npm install --save next react react-dom
將下面腳本添加到 package.json 中:
{ 'scripts': { 'dev': 'next', 'build': 'next build', 'start': 'next start' }}
Next.js 只支持React 16.
由于我們使用 React 16 的特性,所以不得不放棄對(duì) React 15 以及以下版本的支持. 當(dāng)前譯版為7.0.0-canary.8
下面, 文件系統(tǒng)是主要的 API. 每個(gè).js
文件將變成一個(gè)路由,自動(dòng)處理和渲染。
新建 ./pages/index.js
到你的項(xiàng)目中:
export default () => <div>Welcome to next.js!</div>
運(yùn)行 npm run dev
命令并打開(kāi) http://localhost:3000
。 如果你想使用其他端口,可運(yùn)行 npm run dev -- -p <設(shè)置端口號(hào)>
.
目前為止我們可以了解到:
./pages
作為服務(wù)端的渲染和索引./static/
映射到 /static/
(可以 創(chuàng)建一個(gè)靜態(tài)目錄 在你的項(xiàng)目中)這里有個(gè)簡(jiǎn)單的案例,可以下載看看 sample app - nextgram
每個(gè)頁(yè)面只會(huì)導(dǎo)入import
中綁定以及被用到的代碼. 也就是說(shuō)并不會(huì)加載不需要的代碼!
import cowsay from 'cowsay-browser'export default () => <pre> {cowsay.say({ text: 'hi there!' })} </pre>
我們綁定 styled-jsx 來(lái)生成獨(dú)立作用域的 CSS. 目標(biāo)是支持 'shadow CSS',但是 不支持獨(dú)立模塊作用域的 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>
想查看更多案例可以點(diǎn)擊 styled-jsx documentation查看.
有些情況可以使用 CSS 內(nèi)嵌 JS 寫(xiě)法。如下所示:
export default () => <p style={{ color: 'red' }}>hi there</p>
更復(fù)雜的內(nèi)嵌樣式解決方案,特別是服務(wù)端渲染的時(shí)樣式更改。我們可以通過(guò)包裹自定義 Document,來(lái)添加樣式,案例如下:custom <Document>
支持用.css
, .scss
, .less
or .styl
,需要配置默認(rèn)文件 next.config.js,具體可查看下面鏈接
在根目錄下新建文件夾叫static
。代碼可以通過(guò)/static/
來(lái)引入相關(guān)的靜態(tài)資源。
export default () => <img src='/static/my-image.png' alt='my image' />
_注意:不要自定義靜態(tài)文件夾的名字,只能叫static
,因?yàn)橹挥羞@個(gè)名字 Next.js 才會(huì)把它當(dāng)作靜態(tài)資源。
<head>
我們?cè)O(shè)置一個(gè)內(nèi)置組件來(lái)裝載<head>
到頁(yè)面中。
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
屬性來(lái)避免重復(fù)的<head>
標(biāo)簽,保證<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>)
只有第二個(gè)<meta name='viewport' />
才被渲染。
注意:在卸載組件時(shí),<head>
的內(nèi)容將被清除。請(qǐng)確保每個(gè)頁(yè)面都在其<head>
定義了所需要的內(nèi)容,而不是假設(shè)其他頁(yè)面已經(jīng)加過(guò)了
如果你需要一個(gè)有狀態(tài)、生命周期或有初始數(shù)據(jù)的 React 組件(而不是上面的無(wú)狀態(tài)函數(shù)),如下所示:
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> ) }}
相信你注意到,當(dāng)頁(yè)面渲染時(shí)加載數(shù)據(jù),我們使用了一個(gè)異步方法getInitialProps
。它能異步獲取 JS 普通對(duì)象,并綁定在props
上
當(dāng)服務(wù)渲染時(shí),getInitialProps
將會(huì)把數(shù)據(jù)序列化,就像JSON.stringify
。所以確保getInitialProps
返回的是一個(gè)普通 JS 對(duì)象,而不是Date
, Map
或 Set
類型。
當(dāng)頁(yè)面初始化加載時(shí),getInitialProps
只會(huì)加載在服務(wù)端。只有當(dāng)路由跳轉(zhuǎn)(Link
組件跳轉(zhuǎn)或 API 方法跳轉(zhuǎn))時(shí),客戶端才會(huì)執(zhí)行getInitialProps
。
注意:getInitialProps
將不能使用在子組件中。只能使用在pages
頁(yè)面中。
只有服務(wù)端用到的模塊放在
getInitialProps
里,請(qǐng)確保正確的導(dǎo)入了它們,可參考import them properly。 否則會(huì)拖慢你的應(yīng)用速度。
你也可以給無(wú)狀態(tài)組件定義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
入?yún)?duì)象的屬性如下:
pathname
- URL 的 path 部分query
- URL 的 query 部分,并被解析成對(duì)象asPath
- 顯示在瀏覽器中的實(shí)際路徑(包含查詢部分),為String
類型req
- HTTP 請(qǐng)求對(duì)象 (只有服務(wù)器端有)res
- HTTP 返回對(duì)象 (只有服務(wù)器端有)jsonPageRes
- 獲取數(shù)據(jù)響應(yīng)對(duì)象 (只有客戶端有)err
- 渲染過(guò)程中的任何錯(cuò)誤<Link>
用法可以用 <Link>
組件實(shí)現(xiàn)客戶端的路由切換。
// pages/index.jsimport Link from 'next/link'export default () => <div> Click{' '} <Link href='/about'> <a>here</a> </Link>{' '} to read more </div>
// pages/about.jsexport default () => <p>Welcome to About!</p>
注意:可以使用<Link prefetch>
使鏈接和預(yù)加載在后臺(tái)同時(shí)進(jìn)行,來(lái)達(dá)到頁(yè)面的最佳性能。
客戶端路由行為與瀏覽器很相似:
getInitialProps
,數(shù)據(jù)獲取了。如果有錯(cuò)誤情況將會(huì)渲染 _error.js
。pushState
執(zhí)行,新組件被渲染。如果需要注入pathname
, query
或 asPath
到你組件中,你可以使用withRouter。
組件<Link>
接收 URL 對(duì)象,而且它會(huì)自動(dòng)格式化生成 URL 字符串
// pages/index.jsimport 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定義過(guò)的屬性。
<Link>
組件默認(rèn)將新 url 推入路由棧中。你可以使用replace
屬性來(lái)防止添加新輸入。
// pages/index.jsimport Link from 'next/link'export default () => <div> Click{' '} <Link href='/about' replace> <a>here</a> </Link>{' '} to read more </div>
onClick
的組件<Link>
支持任何有onClick
事件的組件。 如果你不包含<a>
標(biāo)簽,它僅給組件添加onClick
事件,而不會(huì)添加href
屬性。
// pages/index.jsimport Link from 'next/link'export default () => <div> Click{' '} <Link href='/about'> <img src='/static/image.png' alt='image' /> </Link> </div>
href
給子元素如子元素是一個(gè)沒(méi)有 href 屬性的<a>
標(biāo)簽,我們將會(huì)指定它以免用戶重復(fù)操作。然而有些時(shí)候,我們需要里面有<a>
標(biāo)簽,但是Link
組件不會(huì)被識(shí)別成超鏈接,結(jié)果不能將href
傳遞給子元素。在這種場(chǎng)景下,你可以定義一個(gè)Link
組件中的布爾屬性passHref
,強(qiáng)制將href
傳遞給子元素。
注意: 使用a
之外的標(biāo)簽而且沒(méi)有通過(guò)passHref
的鏈接可能會(huì)使導(dǎo)航看上去正確,但是當(dāng)搜索引擎爬行檢測(cè)時(shí),將不會(huì)識(shí)別成鏈接(由于缺乏 href 屬性),這會(huì)對(duì)你網(wǎng)站的 SEO 產(chǎn)生負(fù)面影響。
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>
的默認(rèn)行為就是滾到頁(yè)面頂部。當(dāng)有 hash 定義時(shí)(#),頁(yè)面將會(huì)滾動(dòng)到對(duì)應(yīng)的 id 上,就像<a>
標(biāo)簽一樣。為了預(yù)防滾動(dòng)到頂部,可以給<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>
你也可以用next/router
實(shí)現(xiàn)客戶端路由切換
import Router from 'next/router'export default () => <div> Click <span onClick={() => Router.push('/about')}>here</span> to read more </div>
popstate
有些情況(比如使用custom router),你可能想監(jiān)聽(tīng)popstate
,在路由跳轉(zhuǎn)前做一些動(dòng)作。 比如,你可以操作 request 或強(qiáng)制 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
將不會(huì)執(zhí)行popstate
事件。 例如Disabling File-System Routing。
以上Router
對(duì)象的 API 如下:
route
- 當(dāng)前路由的String
類型pathname
- 不包含查詢內(nèi)容的當(dāng)前路徑,為String
類型query
- 查詢內(nèi)容,被解析成Object
類型. 默認(rèn)為{}
asPath
- 展現(xiàn)在瀏覽器上的實(shí)際路徑,包含查詢內(nèi)容,為String
類型push(url, as=url)
- 頁(yè)面渲染第一個(gè)參數(shù) url 的頁(yè)面,瀏覽器欄顯示的是第二個(gè)參數(shù) urlreplace(url, as=url)
- performs a replaceState
call with the given urlbeforePopState(cb=function)
- 在路由器處理事件之前攔截.push
和 replace
函數(shù)的第二個(gè)參數(shù)as
,是為了裝飾 URL 作用。如果你在服務(wù)器端設(shè)置了自定義路由將會(huì)起作用。
push
或 replace
可接收的 URL 對(duì)象(<Link>
組件的 URL 對(duì)象一樣)來(lái)生成 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>
組件一樣添加額外的參數(shù)。
你可以監(jiān)聽(tīng)路由相關(guān)事件。 下面是事件支持列表:
routeChangeStart(url)
- 路由開(kāi)始切換時(shí)觸發(fā)routeChangeComplete(url)
- 完成路由切換時(shí)觸發(fā)routeChangeError(err, url)
- 路由切換報(bào)錯(cuò)時(shí)觸發(fā)beforeHistoryChange(url)
- 瀏覽器 history 模式開(kāi)始切換時(shí)觸發(fā)hashChangeStart(url)
- 開(kāi)始切換 hash 值但是沒(méi)有切換頁(yè)面路由時(shí)觸發(fā)hashChangeComplete(url)
- 完成切換 hash 值但是沒(méi)有切換頁(yè)面路由時(shí)觸發(fā)這里的
url
是指顯示在瀏覽器中的 url。如果你用了Router.push(url, as)
(或類似的方法),那瀏覽器中的 url 將會(huì)顯示 as 的值。
下面是如何正確使用路由事件routeChangeStart
的例子:
const handleRouteChange = url => { console.log('App is changing to: ', url)}Router.events.on('routeChangeStart', handleRouteChange)
如果你不想長(zhǎng)期監(jiān)聽(tīng)該事件,你可以用off
事件去取消監(jiān)聽(tīng):
Router.events.off('routeChangeStart', handleRouteChange)
如果路由加載被取消(比如快速連續(xù)雙擊鏈接)
Router.events.on('routeChangeError', (err, url) => { if (err.cancelled) { console.log(`Route to ${url} was cancelled!`) }})
淺層路由允許你改變 URL 但是不執(zhí)行getInitialProps
生命周期。你可以加載相同頁(yè)面的 URL,得到更新后的路由屬性pathname
和query
,并不失去 state 狀態(tài)。
你可以給Router.push
或 Router.replace
方法加shallow: true
參數(shù)。如下面的例子所示:
// Current URL is '/'const href = '/?counter=10'const as = hrefRouter.push(href, as, { shallow: true })
現(xiàn)在 URL 更新為/?counter=10
。在組件里查看this.props.router.query
你將會(huì)看到更新的 URL。
你可以在componentdidupdate
鉤子函數(shù)中監(jiān)聽(tīng) 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 的參數(shù)改變,比如我們假定有個(gè)其他路由
about
,而你向下面代碼樣運(yùn)行:Router.push('/?counter=10', '/about?counter=10', { shallow: true })
那么這將會(huì)出現(xiàn)新頁(yè)面,即使我們加了淺層路由,但是它還是會(huì)卸載當(dāng)前頁(yè),會(huì)加載新的頁(yè)面并觸發(fā)新頁(yè)面的
getInitialProps
。
如果你想應(yīng)用里每個(gè)組件都處理路由對(duì)象,你可以使用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)
上面路由對(duì)象的 API 可以參考next/router
.
?? 只有生產(chǎn)環(huán)境才有此功能 ??
Next.js 有允許你預(yù)加載頁(yè)面的 API。
用 Next.js 服務(wù)端渲染你的頁(yè)面,可以達(dá)到所有你應(yīng)用里所有未來(lái)會(huì)跳轉(zhuǎn)的路徑即時(shí)響應(yīng),有效的應(yīng)用 Next.js,可以通過(guò)預(yù)加載應(yīng)用程序的功能,最大程度的初始化網(wǎng)站性能。查看更多.
Next.js 的預(yù)加載功能只預(yù)加載 JS 代碼。當(dāng)頁(yè)面渲染時(shí),你可能需要等待數(shù)據(jù)請(qǐng)求。
<Link>
用法你可以給<Link>
添加 prefetch
屬性,Next.js 將會(huì)在后臺(tái)預(yù)加載這些頁(yè)面。
import Link from 'next/link'// example header componentexport 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>
大多數(shù)預(yù)加載是通過(guò)<Link />
處理的,但是我們還提供了命令式 API 用于更復(fù)雜的場(chǎng)景。
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>)
路由實(shí)例只允許在應(yīng)用程序的客戶端。以防服務(wù)端渲染發(fā)生錯(cuò)誤,建議 prefetch 事件寫(xiě)在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)
一般你使用next start
命令來(lái)啟動(dòng) next 服務(wù),你還可以編寫(xiě)代碼來(lái)自定義路由,如使用路由正則等。
當(dāng)使用自定義服務(wù)文件,如下面例子所示叫 server.js 時(shí),確保你更新了 package.json 中的腳本。
{ 'scripts': { 'dev': 'node server.js', 'build': 'next build', 'start': 'NODE_ENV=production node server.js' }}
下面這個(gè)例子使 /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 Babelconst { 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 應(yīng)用是否在開(kāi)發(fā)環(huán)境 - 默認(rèn)false
dir
(string
) Next 項(xiàng)目路徑 - 默認(rèn)'.'
quiet
(boolean
) 是否隱藏包含服務(wù)端消息在內(nèi)的錯(cuò)誤信息 - 默認(rèn)false
conf
(object
) 與next.config.js
的對(duì)象相同 - 默認(rèn){}
生產(chǎn)環(huán)境的話,可以更改 package.json 里的start
腳本為NODE_ENV=production node server.js
。
默認(rèn)情況,Next
將會(huì)把/pages
下的所有文件匹配路由(如/pages/some-file.js
渲染為 site.com/some-file
)
如果你的項(xiàng)目使用自定義路由,那么有可能不同的路由會(huì)得到相同的內(nèi)容,可以優(yōu)化 SEO 和用戶體驗(yàn)。
禁止路由鏈接到/pages
下的文件,只需設(shè)置next.config.js
文件如下所示:
// next.config.jsmodule.exports = { useFileSystemPublicRoutes: false}
注意useFileSystemPublicRoutes
只禁止服務(wù)端的文件路由;但是客戶端的還是禁止不了。
你如果想配置客戶端路由不能跳轉(zhuǎn)文件路由,可以參考Intercepting popstate
。
有時(shí)你需要設(shè)置動(dòng)態(tài)前綴,可以在請(qǐng)求時(shí)設(shè)置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}`) })})
ext.js 支持 JavaScript 的 TC39 提議dynamic import proposal。你可以動(dòng)態(tài)導(dǎo)入 JavaScript 模塊(如 React 組件)。
動(dòng)態(tài)導(dǎo)入相當(dāng)于把代碼分成各個(gè)塊管理。Next.js 服務(wù)端動(dòng)態(tài)導(dǎo)入功能,你可以做很多炫酷事情。
下面介紹一些動(dòng)態(tài)導(dǎo)入方式:
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>
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: () => { 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' />
組件來(lái)初始化頁(yè)面。你可以重寫(xiě)它來(lái)控制頁(yè)面初始化,如下面的事:
componentDidCatch
自定義處理錯(cuò)誤重寫(xiě)的話,新建./pages/_app.js
文件,重寫(xiě) 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> }}
解釋
Next.js
會(huì)自動(dòng)定義文檔標(biāo)記,比如,你從來(lái)不需要添加<html>
, <body>
等。如果想自定義文檔標(biāo)記,你可以新建./pages/_document.js
,然后擴(kuò)展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.jsimport 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
接收到的參數(shù)ctx
對(duì)象都是一樣的
renderPage
是會(huì)執(zhí)行 React 渲染邏輯的函數(shù)(同步),這種做法有助于此函數(shù)支持一些類似于 Aphrodite 的 renderStatic 等一些服務(wù)器端渲染容器。注意:<Main />
外的 React 組件將不會(huì)渲染到瀏覽器中,所以那添加應(yīng)用邏輯代碼。如果你頁(yè)面需要公共組件(菜單或工具欄),可以參照上面說(shuō)的App
組件代替。
404和500錯(cuò)誤客戶端和服務(wù)端都會(huì)通過(guò)error.js
組件處理。如果你想改寫(xiě)它,則新建_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> ) }}
如果你想渲染內(nèi)置錯(cuò)誤頁(yè)面,你可以使用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> ) }}
如果你自定義了個(gè)錯(cuò)誤頁(yè)面,你可以引入自己的錯(cuò)誤頁(yè)面來(lái)代替
next/error
如果你想自定義 Next.js 的高級(jí)配置,可以在根目錄下新建next.config.js
文件(與pages/
和 package.json
一起)
注意:next.config.js
是一個(gè) Node.js 模塊,不是一個(gè) JSON 文件,可以用于 Next 啟動(dòng)服務(wù)已經(jīng)構(gòu)建階段,但是不作用于瀏覽器端。
// next.config.jsmodule.exports = { /* config options here */}
或使用一個(gè)函數(shù):
module.exports = (phase, {defaultConfig}) => { // // https://github.com/zeit/ return { /* config options here */ }}
phase
是配置文件被加載時(shí)的當(dāng)前內(nèi)容。你可看到所有的 phases 常量: 這些常量可以通過(guò)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 */ }}
你可以自定義一個(gè)構(gòu)建目錄,如新建build
文件夾來(lái)代替.next
文件夾成為構(gòu)建目錄。如果沒(méi)有配置構(gòu)建目錄,構(gòu)建時(shí)將會(huì)自動(dòng)新建.next
文件夾
// next.config.jsmodule.exports = { distDir: 'build'}
你可以禁止 etag 生成根據(jù)你的緩存策略。如果沒(méi)有配置,Next 將會(huì)生成 etags 到每個(gè)頁(yè)面中。
// next.config.jsmodule.exports = { generateEtags: false}
Next 暴露一些選項(xiàng)來(lái)給你控制服務(wù)器部署以及緩存頁(yè)面:
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, }}
這個(gè)只是在開(kāi)發(fā)環(huán)境才有的功能。如果你在生成環(huán)境中想緩存 SSR 頁(yè)面,請(qǐng)查看SSR-caching
如 typescript 模塊@zeit/next-typescript
,需要支持解析后綴名為.ts
的文件。pageExtensions
允許你擴(kuò)展后綴名來(lái)解析各種 pages 下的文件。
// next.config.jsmodule.exports = { pageExtensions: ['jsx', 'js']}
Next.js 使用構(gòu)建時(shí)生成的常量來(lái)標(biāo)識(shí)你的應(yīng)用服務(wù)是哪個(gè)版本。在每臺(tái)服務(wù)器上運(yùn)行構(gòu)建命令時(shí),可能會(huì)導(dǎo)致多服務(wù)器部署出現(xiàn)問(wèn)題。為了保持同一個(gè)構(gòu)建 ID,可以配置generateBuildId
函數(shù):
// next.config.jsmodule.exports = { generateBuildId: async () => { // For example get the latest git commit hash here return 'my-build-id' }}
可以使用些一些常見(jiàn)的模塊
注意: webpack
方法將被執(zhí)行兩次,一次在服務(wù)端一次在客戶端。你可以用isServer
屬性區(qū)分客戶端和服務(wù)端來(lái)配置
多配置可以組合在一起,如:
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 }}))
為了擴(kuò)展webpack
使用,可以在next.config.js
定義函數(shù)。
// 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
的第二個(gè)參數(shù)是個(gè)對(duì)象,你可以自定義配置它,對(duì)象屬性如下所示:
buildId
- 字符串類型,構(gòu)建的唯一標(biāo)示dev
- Boolean
型,判斷你是否在開(kāi)發(fā)環(huán)境下isServer
- Boolean
型,為true
使用在服務(wù)端, 為false
使用在客戶端.defaultLoaders
- 對(duì)象型 ,內(nèi)部加載器, 你可以如下配置
babel
- 對(duì)象型,配置babel-loader
.hotSelfAccept
- 對(duì)象型, hot-self-accept-loader
配置選項(xiàng).這個(gè)加載器只能用于高階案例。如 @zeit/next-typescript
添加頂層 typescript 頁(yè)面。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-mdxmodule.exports = { webpack: (config, {}) => { config.module.rules.push({ test: /.mdx/, use: [ options.defaultLoaders.babel, { loader: '@mdx-js/loader', options: pluginOptions.options } ] }) return config }}
為了擴(kuò)展方便我們使用babel
,可以在應(yīng)用根目錄新建.babelrc
文件,該文件可配置。
如果有該文件,我們將會(huì)考慮數(shù)據(jù)源,因此也需要定義 next 項(xiàng)目需要的東西,也就是 next/babel
預(yù)設(shè)。
這種設(shè)計(jì)方案將會(huì)使你不詫異于我們可以定制 babel 配置。
下面是.babelrc
文件案例:
{ 'presets': ['next/babel'], 'plugins': []}
next/babel
預(yù)設(shè)可處理各種 React 應(yīng)用所需要的情況。包括:
presets / plugins 不允許添加到.babelrc
中,然而你可以配置next/babel
預(yù)設(shè):
{ 'presets': [ ['next/babel', { 'preset-env': {}, 'transform-runtime': {}, 'styled-jsx': {}, 'class-properties': {} }] ], 'plugins': []}
'preset-env'
模塊選項(xiàng)應(yīng)該保持為 false,否則 webpack 代碼分割將被禁用。
next/config
模塊使你應(yīng)用運(yùn)行時(shí)可以讀取些存儲(chǔ)在next.config.js
的配置項(xiàng)。serverRuntimeConfig
屬性只在服務(wù)器端可用,publicRuntimeConfig
屬性在服務(wù)端和客戶端可用。
// next.config.jsmodule.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.jsimport 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 sideconsole.log(publicRuntimeConfig.staticFolder) // Will be available on both server and clientexport default () => <div> <img src={`${publicRuntimeConfig.staticFolder}/logo.png`} alt='logo' /></div>
啟動(dòng)開(kāi)發(fā)環(huán)境服務(wù)可以設(shè)置不同的 hostname,你可以在啟動(dòng)命令后面加上--hostname 主機(jī)名
或 -H 主機(jī)名
。它將會(huì)啟動(dòng)一個(gè) TCP 服務(wù)器來(lái)監(jiān)聽(tīng)連接所提供的主機(jī)。
建立一個(gè) CDN,你能配置assetPrefix
選項(xiàng),去配置你的 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 運(yùn)行時(shí)將會(huì)自動(dòng)添加前綴,但是對(duì)于/static
是沒(méi)有效果的,如果你想這些靜態(tài)資源也能使用 CDN,你需要自己添加前綴。有一個(gè)方法可以判斷你的環(huán)境來(lái)加前綴,如 in this example。
部署中,你可以先構(gòu)建打包生成環(huán)境代碼,再啟動(dòng)服務(wù)。因此,構(gòu)建和啟動(dòng)分為下面兩條命令:
next buildnext start
例如,使用now
去部署package.json
配置文件如下:
{ 'name': 'my-app', 'dependencies': { 'next': 'latest' }, 'scripts': { 'dev': 'next', 'build': 'next build', 'start': 'next start' }}
然后就可以直接運(yùn)行now
了。
Next.js 也有其他托管解決方案。請(qǐng)查考 wiki 章節(jié)'Deployment' 。
注意:NODE_ENV
可以通過(guò)next
命令配置,如果沒(méi)有配置,會(huì)最大渲染,如果你使用編程式寫(xiě)法的話programmatically,你需要手動(dòng)設(shè)置NODE_ENV=production
。
注意:推薦將.next
或自定義打包文件夾custom dist folder放入.gitignore
或 .npmignore
中。否則,使用files
或 now.files
添加部署白名單,并排除.next
或自定義打包文件夾。
Next.js 支持 IE11 和所有的現(xiàn)代瀏覽器使用了@babel/preset-env
。為了支持 IE11,Next.js 需要全局添加Promise
的 polyfill。有時(shí)你的代碼或引入的其他 NPM 包的部分功能現(xiàn)代瀏覽器不支持,則需要用 polyfills 去實(shí)現(xiàn)。
ployflls 實(shí)現(xiàn)案例為polyfills。
next export
可以輸出一個(gè) Next.js 應(yīng)用作為靜態(tài)資源應(yīng)用而不依靠 Node.js 服務(wù)。 這個(gè)輸出的應(yīng)用幾乎支持 Next.js 的所有功能,包括動(dòng)態(tài)路由,預(yù)獲取,預(yù)加載以及動(dòng)態(tài)導(dǎo)入。
next export
將把所有有可能渲染出的 HTML 都生成。這是基于映射對(duì)象的pathname
關(guān)鍵字關(guān)聯(lián)到頁(yè)面對(duì)象。這個(gè)映射叫做exportPathMap
。
頁(yè)面對(duì)象有2個(gè)屬性:
page
- 字符串類型,頁(yè)面生成目錄query
- 對(duì)象類型,當(dāng)預(yù)渲染時(shí),query
對(duì)象將會(huì)傳入頁(yè)面的生命周期getInitialProps
中。默認(rèn)為{}
。通常開(kāi)發(fā) Next.js 應(yīng)用你將會(huì)運(yùn)行:
next buildnext export
next export
命令默認(rèn)不需要任何配置,將會(huì)自動(dòng)生成默認(rèn)exportPathMap
生成pages
目錄下的路由你頁(yè)面。
如果你想動(dòng)態(tài)配置路由,可以在next.config.js
中添加異步函數(shù)exportPathMap
。
// next.config.jsmodule.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 的結(jié)尾是目錄名,則將導(dǎo)出
/dir-name/index.html
,但是如果結(jié)尾有擴(kuò)展名,將會(huì)導(dǎo)出對(duì)應(yīng)的文件,如上/readme.md
。如果你使用.html
以外的擴(kuò)展名解析文件時(shí),你需要設(shè)置 header 的Content-Type
頭為'text/html'.
輸入下面命令:
next buildnext export
你可以在package.json
添加一個(gè) NPM 腳本,如下所示:
{ 'scripts': { 'build': 'next build', 'export': 'npm run build && next export' }}
接著只用執(zhí)行一次下面命令:
npm run export
然后你將會(huì)有一個(gè)靜態(tài)頁(yè)面應(yīng)用在out
目錄下。
你也可以自定義輸出目錄。可以運(yùn)行
next export -h
命令查看幫助。
現(xiàn)在你可以部署out
目錄到任意靜態(tài)資源服務(wù)器上。注意如果部署 GitHub Pages 需要加個(gè)額外的步驟,文檔如下
例如,訪問(wèn)out
目錄并用下面命令部署應(yīng)用ZEIT Now.
now
使用next export
,我們創(chuàng)建了個(gè)靜態(tài) HTML 應(yīng)用。構(gòu)建時(shí)將會(huì)運(yùn)行頁(yè)面里生命周期getInitialProps
函數(shù)。
req
和res
只在服務(wù)端可用,不能通過(guò)getInitialProps
。
所以你不能預(yù)構(gòu)建 HTML 文件時(shí)動(dòng)態(tài)渲染 HTML 頁(yè)面。如果你想動(dòng)態(tài)渲染可以運(yùn)行
next start
或其他自定義服務(wù)端 API。
一個(gè) zone 時(shí)一個(gè)單獨(dú)的 Next.js 應(yīng)用。如果你有很多 zone,你可以合并成一個(gè)應(yīng)用。
例如,你如下有兩個(gè) zone:
/docs/**
有多 zone 應(yīng)用技術(shù)支持,你可以將幾個(gè)應(yīng)用合并到一個(gè),而且可以自定義 URL 路徑,使你能同時(shí)單獨(dú)開(kāi)發(fā)各個(gè)應(yīng)用。
與 microservices 觀念類似, 只是應(yīng)用于前端應(yīng)用.
zone 沒(méi)有單獨(dú)的 API 文檔。你需要做下面事即可:
/docs/**
)你能使用 HTTP 代理合并 zone
你能使用代理micro proxy來(lái)作為你的本地代理服務(wù)。它允許你定義路由規(guī)則如下:
{ 'rules': [ {'pathname': '/docs**', 'method':['GET', 'POST', 'OPTIONS'], 'dest': 'https://docs.my-app.com'}, {'pathname': '/**', 'dest': 'https://ui.my-app.com'} ]}
生產(chǎn)環(huán)境部署,如果你使用了ZEIT now,可以它的使用path alias 功能。否則,你可以設(shè)置你已使用的代理服務(wù)編寫(xiě)上面規(guī)則來(lái)路由 HTML 頁(yè)面
它的開(kāi)發(fā)體驗(yàn)和終端用戶體驗(yàn)都很好,所以我們決定開(kāi)源出來(lái)給大家共享。
客戶端大小根據(jù)應(yīng)用需求不一樣大小也不一樣。
一個(gè)最簡(jiǎn)單 Next 應(yīng)該用 gzip 壓縮后大約65kb
是或不是.
是,因?yàn)樗屇愕?SSR 開(kāi)發(fā)更簡(jiǎn)單。
不是,因?yàn)樗?guī)定了一定的目錄結(jié)構(gòu),使我們能做以下更高級(jí)的事:
此外,Next.js 還提供兩個(gè)內(nèi)置特性:
<Link>
(通過(guò)引入 next/link
)<head>
的組件: <Head>
(通過(guò)引入 next/head
)如果你想寫(xiě)共用組件,可以嵌入 Next.js 應(yīng)用和 React 應(yīng)用中,推薦使用create-react-app
。你可以更改import
保持代碼清晰。
Next.js 自帶styled-jsx庫(kù)支持 CSS 嵌入 JS。而且你可以選擇其他你喜歡的嵌入方法到你的項(xiàng)目中,可參考文檔嵌入樣式。
我們遵循 V8 引擎的,如今 V8 引擎廣泛支持 ES6 語(yǔ)法以及async
和await
語(yǔ)法,所以我們支持轉(zhuǎn)換它們。但是 V8 引擎不支持修飾器語(yǔ)法,所以我們也不支持轉(zhuǎn)換這語(yǔ)法。
Next.js 的特別之處如下所示:
getInitialProps
來(lái)阻止路由加載(當(dāng)服務(wù)端渲染或路由懶加載時(shí))因此,我們可以介紹一個(gè)非常簡(jiǎn)單的路由方法,它由下面兩部分組成:
url
對(duì)象,來(lái)檢查 url 或修改歷史記錄<Link />
組件用于包裝如(<a/>
)標(biāo)簽的元素容器,來(lái)執(zhí)行客戶端轉(zhuǎn)換。我們使用了些有趣的場(chǎng)景來(lái)測(cè)試路由的靈活性,例如,可查看nextgram。
我們通過(guò)請(qǐng)求處理來(lái)添加任意 URL 與任意組件之前的映射關(guān)系。
在客戶端,我們<Link>
組件有個(gè)屬性as
,可以裝飾改變獲取到的 URL。
這由你決定。getInitialProps
是一個(gè)異步函數(shù)async
(也就是函數(shù)將會(huì)返回個(gè)Promise
)。你可以在任意位置獲取數(shù)據(jù)。
是的! 這里有個(gè)例子Apollo.
是的! 這里有個(gè)例子
從我們第一次發(fā)版就已經(jīng)提供很多例子,你可以查看這些例子。
我們實(shí)現(xiàn)的大部分目標(biāo)都是通過(guò) Guillermo Rauch 的Web 應(yīng)用的7原則來(lái)啟發(fā)出的。
PHP 的易用性也是個(gè)很好的靈感來(lái)源,我們覺(jué)得 Next.js 可以替代很多需要用 PHP 輸出 HTML 的場(chǎng)景。
與 PHP 不同的是,我們得利于 ES6 模塊系統(tǒng),每個(gè)文件會(huì)輸出一個(gè)組件或方法,以便可以輕松的導(dǎo)入用于懶加載和測(cè)試
我們研究 React 的服務(wù)器渲染時(shí)并沒(méi)有花費(fèi)很大的步驟,因?yàn)槲覀儼l(fā)現(xiàn)一個(gè)類似于 Next.js 的產(chǎn)品,React 作者 Jordan Walke 寫(xiě)的react-page (現(xiàn)在已經(jīng)廢棄)
聯(lián)系客服