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