Next.js 是一個輕量級的 React 服務端渲染應用框架。
Next.js中文站點 nextjs.frontendx.cn
Next.js中文站Github github.com/raoenhui/ne…
當前翻譯版本為 7.0.0-canary.8。
- 怎麼使用
- 專案部署
- 瀏覽器支援
- 匯出靜態頁面
- 多 zone
- 技巧
- FAQ
- 貢獻
- 作者
怎麼使用
安裝
安裝它:
npm install --save next react react-dom
複製程式碼
將下面指令碼新增到 package.json 中:
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
複製程式碼
下面, 檔案系統是主要的 API. 每個.js
檔案將變成一個路由,自動處理和渲染。
新建 ./pages/index.js
到你的專案中:
export default () => <div>Welcome to next.js!</div>
複製程式碼
執行 npm run dev
命令並開啟 http://localhost:3000
。 如果你想使用其他埠,可執行 npm run dev -- -p <設定埠號>
.
目前為止我們可以瞭解到:
- 自動打包編譯 (使用 webpack 和 babel)
- 熱載入
- 以
./pages
作為服務端的渲染和索引 - Static file serving.
./static/
is mapped to/static/
(given you create a./static/
directory inside your project) - 靜態檔案服務.
./static/
對映到/static/
(可以 建立一個靜態目錄 在你的專案中)
這裡有個簡單的案例,可以下載看看 sample app - nextgram
程式碼自動分割
每個頁面只會匯入import
中繫結以及被用到的程式碼. 也就是說並不會載入不需要的程式碼!
import cowsay from 'cowsay-browser'
export default () =>
<pre>
{cowsay.say({ text: 'hi there!' })}
</pre>
複製程式碼
CSS
支援嵌入樣式
Examples
我們繫結 styled-jsx 來生成獨立作用域的 CSS. 目標是支援 "shadow CSS",但是 不支援獨立模組作用域的 JS.
export default () =>
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
p {
color: blue;
}
div {
background: red;
}
@media (max-width: 600px) {
div {
background: blue;
}
}
`}</style>
<style global jsx>{`
body {
background: black;
}
`}</style>
</div>
複製程式碼
想檢視更多案例可以點選 styled-jsx documentation檢視.
內嵌樣式
有些情況可以使用 CSS 內嵌 JS 寫法。如下所示:
export default () => <p style={{ color: 'red' }}>hi there</p>
複製程式碼
更復雜的內嵌樣式解決方案,特別是服務端渲染的時樣式更改。我們可以通過包裹自定義 Document,來新增樣式,案例如下:custom <Document>
使用 CSS / Sass / Less / Stylus files
支援用.css
, .scss
, .less
or .styl
,需要配置預設檔案 next.config.js,具體可檢視下面連結
靜態檔案服務(如影像)
在根目錄下新建資料夾叫static
。程式碼可以通過/static/
來引入相關的靜態資源。
export default () => <img src="/static/my-image.png" alt="my image" />
複製程式碼
_注意:不要自定義靜態資料夾的名字,只能叫static
,因為只有這個名字 Next.js 才會把它當作靜態資源。
生成<head>
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>
複製程式碼
我們定義key
屬性來避免重複的<head>
標籤,保證<head>
只渲染一次,如下所示:
import Head from 'next/head'
export default () => (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" key="viewport" />
</Head>
<Head>
<meta name="viewport" content="initial-scale=1.2, width=device-width" key="viewport" />
</Head>
<p>Hello world!</p>
</div>
)
複製程式碼
只有第二個<meta name="viewport" />
才被渲染。
注意:在解除安裝元件時,<head>
的內容將被清除。請確保每個頁面都在其<head>
定義了所需要的內容,而不是假設其他頁面已經加過了
獲取資料以及元件生命週期
Examples
如果你需要一個有狀態、生命週期或有初始資料的 React 元件(而不是上面的無狀態函式),如下所示:
import React from 'react'
export default class extends React.Component {
static async getInitialProps({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
return { userAgent }
}
render() {
return (
<div>
Hello World {this.props.userAgent}
</div>
)
}
}
複製程式碼
相信你注意到,當頁面渲染時載入資料,我們使用了一個非同步方法getInitialProps
。它能非同步獲取 JS 普通物件,並繫結在props
上
當服務渲染時,getInitialProps
將會把資料序列化,就像JSON.stringify
。所以確保getInitialProps
返回的是一個普通 JS 物件,而不是Date
, Map
或 Set
型別。
當頁面初次載入時,getInitialProps
只會在服務端執行一次。getInitialProps
只有在路由切換的時候(如Link
元件跳轉或路由自定義跳轉)時,客戶端的才會被執行。
當頁面初始化載入時,getInitialProps
只會載入在服務端。只有當路由跳轉(Link
元件跳轉或 API 方法跳轉)時,客戶端才會執行getInitialProps
。
注意:getInitialProps
將不能使用在子元件中。只能使用在pages
頁面中。
只有服務端用到的模組放在
getInitialProps
裡,請確保正確的匯入了它們,可參考import them properly。 否則會拖慢你的應用速度。
你也可以給無狀態元件定義getInitialProps
:
const Page = ({ stars }) =>
<div>
Next stars: {stars}
</div>
Page.getInitialProps = async ({ req }) => {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
export default Page
複製程式碼
getInitialProps
入參物件的屬性如下:
pathname
- URL 的 path 部分query
- URL 的 query 部分,並被解析成物件asPath
- 顯示在瀏覽器中的實際路徑(包含查詢部分),為String
型別req
- HTTP 請求物件 (只有伺服器端有)res
- HTTP 返回物件 (只有伺服器端有)jsonPageRes
- 獲取資料響應物件 (只有客戶端有)err
- 渲染過程中的任何錯誤
路由
<Link>
用法
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
執行,新元件被渲染。
如果需要注入pathname
, query
或 asPath
到你元件中,你可以使用withRouter。
URL 物件
Examples
元件<Link>
接收 URL 物件,而且它會自動格式化生成 URL 字串
// pages/index.js
import Link from 'next/link'
export default () =>
<div>
Click{' '}
<Link href={{ pathname: '/about', query: { name: 'Zeit' }}}>
<a>here</a>
</Link>{' '}
to read more
</div>
複製程式碼
將生成 URL 字串/about?name=Zeit
,你可以使用任何在Node.js URL module documentation定義過的屬性。
替換路由
<Link>
元件預設將新 url 推入路由棧中。你可以使用replace
屬性來防止新增新輸入。
// pages/index.js
import Link from 'next/link'
export default () =>
<div>
Click{' '}
<Link href="/about" replace>
<a>here</a>
</Link>{' '}
to read more
</div>
複製程式碼
元件支援點選事件 onClick
<Link>
支援每個元件所支援的onClick
事件。如果你不提供<a>
標籤,只會處理onClick
事件而href
將不起作用。
// pages/index.js
import Link from 'next/link'
export default () =>
<div>
Click{' '}
<Link href="/about">
<img src="/static/image.png" alt="image" />
</Link>
</div>
複製程式碼
暴露 href
給子元素
如子元素是一個沒有 href 屬性的<a>
標籤,我們將會指定它以免使用者重複操作。然而有些時候,我們需要裡面有<a>
標籤,但是Link
元件不會被識別成超連結,結果不能將href
傳遞給子元素。在這種場景下,你可以定義一個Link
元件中的布林屬性passHref
,強制將href
傳遞給子元素。
注意: 使用a
之外的標籤而且沒有通過passHref
的連結可能會使導航看上去正確,但是當搜尋引擎爬行檢測時,將不會識別成連結(由於缺乏 href 屬性),這會對你網站的 SEO 產生負面影響。
import Link from 'next/link'
import Unexpected_A from 'third-library'
export default ({ href, name }) =>
<Link href={href} passHref>
<Unexpected_A>
{name}
</Unexpected_A>
</Link>
複製程式碼
禁止滾動到頁面頂部
<Link>
的預設行為就是滾到頁面頂部。當有 hash 定義時(#),頁面將會滾動到對應的 id 上,就像<a>
標籤一樣。為了預防滾動到頂部,可以給<Link>
加
scroll={false}
屬性:
<Link scroll={false} href="/?counter=10"><a>Disables scrolling</a></Link>
<Link href="/?counter=10"><a>Changes with scrolling to top</a></Link>
複製程式碼
命令式
你也可以用next/router
實現客戶端路由切換
import Router from 'next/router'
export default () =>
<div>
Click <span onClick={() => Router.push('/about')}>here</span> to read more
</div>
複製程式碼
攔截器 popstate
有些情況(比如使用custom router),你可能想監聽popstate
,在路由跳轉前做一些動作。
比如,你可以操作 request 或強制 SSR 重新整理
import Router from 'next/router'
Router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as !== "/" || as !== "/other") {
// Have SSR render bad routes as a 404.
window.location.href = as
return false
}
return true
});
複製程式碼
如果你在beforePopState
中返回 false,Router
將不會執行popstate
事件。
例如Disabling File-System Routing。
以上Router
物件的 API 如下:
route
- 當前路由的String
型別pathname
- 不包含查詢內容的當前路徑,為String
型別query
- 查詢內容,被解析成Object
型別. 預設為{}
asPath
- 展現在瀏覽器上的實際路徑,包含查詢內容,為String
型別push(url, as=url)
- 頁面渲染第一個引數 url 的頁面,瀏覽器欄顯示的是第二個引數 urlreplace(url, as=url)
- performs areplaceState
call with the given urlbeforePopState(cb=function)
- 在路由器處理事件之前攔截.
push
和 replace
函式的第二個引數as
,是為了裝飾 URL 作用。如果你在伺服器端設定了自定義路由將會起作用。
URL 物件用法
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>
元件一樣新增額外的引數。
路由事件
你可以監聽路由相關事件。 下面是事件支援列表:
routeChangeStart(url)
- 路由開始切換時觸發routeChangeComplete(url)
- 完成路由切換時觸發routeChangeError(err, url)
- 路由切換報錯時觸發beforeHistoryChange(url)
- 瀏覽器 history 模式開始切換時觸發hashChangeStart(url)
- 開始切換 hash 值但是沒有切換頁面路由時觸發hashChangeComplete(url)
- 完成切換 hash 值但是沒有切換頁面路由時觸發
這裡的
url
是指顯示在瀏覽器中的 url。如果你用了Router.push(url, as)
(或類似的方法),那瀏覽器中的 url 將會顯示 as 的值。
下面是如何正確使用路由事件routeChangeStart
的例子:
const handleRouteChange = url => {
console.log('App is changing to: ', url)
}
Router.events.on('routeChangeStart', handleRouteChange)
複製程式碼
如果你不想長期監聽該事件,你可以用off
事件去取消監聽:
Router.events.off('routeChangeStart', handleRouteChange)
複製程式碼
如果路由載入被取消(比如快速連續雙擊連結)
Router.events.on('routeChangeError', (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
})
複製程式碼
淺層路由
Examples
淺層路由允許你改變 URL 但是不執行getInitialProps
生命週期。你可以載入相同頁面的 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.router.query
你將會看到更新的 URL。
你可以在componentdidupdate
鉤子函式中監聽 URL 的變化。
componentDidUpdate(prevProps) {
const { pathname, query } = this.props.router
// verify props have changed to avoid an infinite loop
if (query.id !== prevProps.router.query.id) {
// fetch data based on the new query
}
}
複製程式碼
注意:
淺層路由只作用於相同 URL 的引數改變,比如我們假定有個其他路由
about
,而你向下面程式碼樣執行:
Router.push('/?counter=10', '/about?counter=10', { shallow: true }) 複製程式碼
那麼這將會出現新頁面,即使我們加了淺層路由,但是它還是會解除安裝當前頁,會載入新的頁面並觸發新頁面的
getInitialProps
。
高階元件
Examples
如果你想應用裡每個元件都處理路由物件,你可以使用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
.
預載入頁面
⚠️ 只有生產環境才有此功能 ⚠️
Examples
Next.js 有允許你預載入頁面的 API。
用 Next.js 服務端渲染你的頁面,可以達到所有你應用裡所有未來會跳轉的路徑即時響應,有效的應用 Next.js,可以通過預載入應用程式的功能,最大程度的初始化網站效能。檢視更多.
Next.js 的預載入功能只預載入 JS 程式碼。當頁面渲染時,你可能需要等待資料請求。
<Link>
用法
你可以給新增 prefetch
屬性,Next.js 將會在後臺預載入這些頁面。
import Link from 'next/link'
// example header component
export default () =>
<nav>
<ul>
<li>
<Link prefetch href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link prefetch href="/about">
<a>About</a>
</Link>
</li>
<li>
<Link prefetch href="/contact">
<a>Contact</a>
</Link>
</li>
</ul>
</nav>
複製程式碼
命令式 prefetch 寫法
大多數預載入是通過處理的,但是我們還提供了命令式 API 用於更復雜的場景。
import { withRouter } from 'next/router'
export default withRouter(({ router }) =>
<div>
<a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
A route transition will happen after 100ms
</a>
{// but we can prefetch it!
router.prefetch('/dynamic')}
</div>
)
複製程式碼
路由例項只允許在應用程式的客戶端。以防服務端渲染髮生錯誤,建議 prefetch 事件寫在componentDidMount()
生命週期裡。
import React from 'react'
import { withRouter } from 'next/router'
class MyLink extends React.Component {
componentDidMount() {
const { router } = this.props
router.prefetch('/dynamic')
}
render() {
const { router } = this.props
return (
<div>
<a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
A route transition will happen after 100ms
</a>
</div>
)
}
}
export default withRouter(MyLink)
複製程式碼
自定義服務端路由
Examples
一般你使用next start
命令來啟動 next 服務,你還可以編寫程式碼來自定義路由,如使用路由正則等。
當使用自定義服務檔案,如下面例子所示叫 server.js 時,確保你更新了 package.json 中的指令碼。
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}
複製程式碼
下面這個例子使 /a
路由解析為./pages/b
,以及/b
路由解析為./pages/a
;
// This file doesn't go through babel or webpack transformation.
// Make sure the syntax and sources this file requires are compatible with the current node version you are running
// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
複製程式碼
next
的 API 如下所示
next(opts: object)
opts 的屬性如下:
dev
(boolean
) 判斷 Next.js 應用是否在開發環境 - 預設false
dir
(string
) Next 專案路徑 - 預設'.'
quiet
(boolean
) 是否隱藏包含服務端訊息在內的錯誤資訊 - 預設false
conf
(object
) 與next.config.js
的物件相同 - 預設{}
生產環境的話,可以更改 package.json 裡的start
指令碼為NODE_ENV=production node server.js
。
禁止檔案路由
預設情況,Next
將會把/pages
下的所有檔案匹配路由(如/pages/some-file.js
渲染為 site.com/some-file
)
如果你的專案使用自定義路由,那麼有可能不同的路由會得到相同的內容,可以優化 SEO 和使用者體驗。
禁止路由連結到/pages
下的檔案,只需設定next.config.js
檔案如下所示:
// next.config.js
module.exports = {
useFileSystemPublicRoutes: false
}
複製程式碼
注意useFileSystemPublicRoutes
只禁止服務端的檔案路由;但是客戶端的還是禁止不了。
你如果想配置客戶端路由不能跳轉檔案路由,可以參考Intercepting popstate
。
動態字首
有時你需要設定動態字首,可以在請求時設定assetPrefix
改變字首。
使用方法如下:
const next = require('next')
const micro = require('micro')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handleNextRequests = app.getRequestHandler()
app.prepare().then(() => {
const server = micro((req, res) => {
// Add assetPrefix support based on the hostname
if (req.headers.host === 'my-app.com') {
app.setAssetPrefix('http://cdn.com/myapp')
} else {
app.setAssetPrefix('')
}
handleNextRequests(req, res)
})
server.listen(port, (err) => {
if (err) {
throw err
}
console.log(`> Ready on http://localhost:${port}`)
})
})
複製程式碼
動態匯入
Examples
ext.js 支援 JavaScript 的 TC39 提議dynamic import proposal。你可以動態匯入 JavaScript 模組(如 React 元件)。
動態匯入相當於把程式碼分成各個塊管理。Next.js 服務端動態匯入功能,你可以做很多炫酷事情。
下面介紹一些動態匯入方式:
1. 基礎支援 (同樣支援 SSR)
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(import('../components/hello'))
export default () =>
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
複製程式碼
2. 自定義載入元件
import dynamic from 'next/dynamic'
const DynamicComponentWithCustomLoading = dynamic(
import('../components/hello2'),
{
loading: () => <p>...</p>
}
)
export default () =>
<div>
<Header />
<DynamicComponentWithCustomLoading />
<p>HOME PAGE is here!</p>
</div>
複製程式碼
3. 禁止使用 SSR
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(import('../components/hello3'), {
ssr: false
})
export default () =>
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
複製程式碼
4. 同時載入多個模組
import dynamic from 'next/dynamic'
const HelloBundle = dynamic({
modules: () => {
const components = {
Hello1: import('../components/hello1'),
Hello2: import('../components/hello2')
}
return components
},
render: (props, { Hello1, Hello2 }) =>
<div>
<h1>
{props.title}
</h1>
<Hello1 />
<Hello2 />
</div>
})
export default () => <HelloBundle title="Dynamic Bundle" />
複製程式碼
自定義 <App>
元件來初始化頁面。你可以重寫它來控制頁面初始化,如下面的事:
- 當頁面變化時保持頁面佈局
- 當路由變化時保持頁面狀態
- 使用
componentDidCatch
自定義處理錯誤 - 注入額外資料到頁面裡 (如 GraphQL 查詢)
重寫的話,新建./pages/_app.js
檔案,重寫 App 模組如下所示:
import App, {Container} from 'next/app'
import React from 'react'
export default class MyApp extends App {
static async getInitialProps ({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return {pageProps}
}
render () {
const {Component, pageProps} = this.props
return <Container>
<Component {...pageProps} />
</Container>
}
}
複製程式碼
自定義 <Document>
- 在服務端呈現
- 初始化服務端時新增文件標記元素
- 通常實現服務端渲染會使用一些 css-in-js 庫,如styled-components, glamorous 或 emotion。styled-jsx是 Next.js 自帶預設使用的 css-in-js 庫
Next.js
會自動定義文件標記,比如,你從來不需要新增<html>
, <body>
等。如果想自定義文件標記,你可以新建./pages/_document.js
,然後擴充套件Document
類:
// _document is only rendered on the server side and not on the client side
// Event handlers like onClick can't be added to this file
// ./pages/_document.js
import Document, { Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<html>
<Head>
<style>{`body { margin: 0 } /* custom! */`}</style>
</Head>
<body className="custom_class">
<Main />
<NextScript />
</body>
</html>
)
}
}
複製程式碼
鉤子getInitialProps
接收到的引數ctx
物件都是一樣的
- 回撥函式
renderPage
是會執行 React 渲染邏輯的函式(同步),這種做法有助於此函式支援一些類似於 Aphrodite 的 renderStatic 等一些伺服器端渲染容器。
注意:<Main />
外的 React 元件將不會渲染到瀏覽器中,所以那新增應用邏輯程式碼。如果你頁面需要公共元件(選單或工具欄),可以參照上面說的App
元件代替。
自定義錯誤處理
404和500錯誤客戶端和服務端都會通過error.js
元件處理。如果你想改寫它,則新建_error.js
在資料夾中:
import React from 'react'
export default class Error extends React.Component {
static getInitialProps({ res, err }) {
const statusCode = res ? res.statusCode : err ? err.statusCode : null;
return { statusCode }
}
render() {
return (
<p>
{this.props.statusCode
? `An error ${this.props.statusCode} occurred on server`
: 'An error occurred on client'}
</p>
)
}
}
複製程式碼
渲染內建錯誤頁面
如果你想渲染內建錯誤頁面,你可以使用next/error
:
import React from 'react'
import Error from 'next/error'
import fetch from 'isomorphic-unfetch'
export default class Page extends React.Component {
static async getInitialProps() {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const statusCode = res.statusCode > 200 ? res.statusCode : false
const json = await res.json()
return { statusCode, stars: json.stargazers_count }
}
render() {
if (this.props.statusCode) {
return <Error statusCode={this.props.statusCode} />
}
return (
<div>
Next stars: {this.props.stars}
</div>
)
}
}
複製程式碼
如果你自定義了個錯誤頁面,你可以引入自己的錯誤頁面來代替
next/error
自定義配置
如果你想自定義 Next.js 的高階配置,可以在根目錄下新建next.config.js
檔案(與pages/
和 package.json
一起)
注意:next.config.js
是一個 Node.js 模組,不是一個 JSON 檔案,可以用於 Next 啟動服務已經構建階段,但是不作用於瀏覽器端。
// next.config.js
module.exports = {
/* config options here */
}
複製程式碼
或使用一個函式:
module.exports = (phase, {defaultConfig}) => {
//
// https://github.com/zeit/
return {
/* config options here */
}
}
複製程式碼
phase
是配置檔案被載入時的當前內容。你可看到所有的 phases 常量:constants
這些常量可以通過next/constants
引入:
const {PHASE_DEVELOPMENT_SERVER} = require('next/constants')
module.exports = (phase, {defaultConfig}) => {
if(phase === PHASE_DEVELOPMENT_SERVER) {
return {
/* development only config options here */
}
}
return {
/* config options for all phases except development here */
}
}
複製程式碼
設定自定義構建目錄
你可以自定義一個構建目錄,如新建build
資料夾來代替.next
資料夾成為構建目錄。如果沒有配置構建目錄,構建時將會自動新建.next
資料夾
// next.config.js
module.exports = {
distDir: 'build'
}
複製程式碼
禁止 etag 生成
你可以禁止 etag 生成根據你的快取策略。如果沒有配置,Next 將會生成 etags 到每個頁面中。
// next.config.js
module.exports = {
generateEtags: false
}
複製程式碼
配置 onDemandEntries
Next 暴露一些選項來給你控制伺服器部署以及快取頁面:
module.exports = {
onDemandEntries: {
// period (in ms) where the server will keep pages in the buffer
maxInactiveAge: 25 * 1000,
// number of pages that should be kept simultaneously without being disposed
pagesBufferLength: 2,
}
}
複製程式碼
這個只是在開發環境才有的功能。如果你在生成環境中想快取 SSR 頁面,請檢視SSR-caching
配置頁面字尾名解析擴充套件
如 typescript 模組@zeit/next-typescript
,需要支援解析字尾名為.ts
的檔案。pageExtensions
允許你擴充套件字尾名來解析各種 pages 下的檔案。
// next.config.js
module.exports = {
pageExtensions: ['jsx', 'js']
}
複製程式碼
配置構建 ID
Next.js 使用構建時生成的常量來標識你的應用服務是哪個版本。在每臺伺服器上執行構建命令時,可能會導致多伺服器部署出現問題。為了保持同一個構建 ID,可以配置generateBuildId
函式:
// next.config.js
module.exports = {
generateBuildId: async () => {
// For example get the latest git commit hash here
return 'my-build-id'
}
}
複製程式碼
自定義 webpack 配置
Examples
可以使用些一些常見的模組
注意: webpack
方法將被執行兩次,一次在服務端一次在客戶端。你可以用isServer
屬性區分客戶端和服務端來配置
多配置可以組合在一起,如:
const withTypescript = require('@zeit/next-typescript')
const withSass = require('@zeit/next-sass')
module.exports = withTypescript(withSass({
webpack(config, options) {
// Further custom configuration here
return config
}
}))
複製程式碼
為了擴充套件webpack
使用,可以在next.config.js
定義函式。
// next.config.js is not transformed by Babel. So you can only use javascript features supported by your version of Node.js.
module.exports = {
webpack: (config, { buildId, dev, isServer, defaultLoaders }) => {
// Perform customizations to webpack config
// Important: return the modified config
return config
},
webpackDevMiddleware: config => {
// Perform customizations to webpack dev middleware config
// Important: return the modified config
return config
}
}
複製程式碼
webpack
的第二個引數是個物件,你可以自定義配置它,物件屬性如下所示:
buildId
- 字串型別,構建的唯一標示dev
-Boolean
型,判斷你是否在開發環境下isServer
-Boolean
型,為true
使用在服務端, 為false
使用在客戶端.defaultLoaders
- 物件型 ,內部載入器, 你可以如下配置babel
- 物件型,配置babel-loader
.hotSelfAccept
- 物件型,hot-self-accept-loader
配置選項.這個載入器只能用於高階案例。如@zeit/next-typescript
新增頂層 typescript 頁面。
defaultLoaders.babel
使用案例如下:
// Example next.config.js for adding a loader that depends on babel-loader
// This source was taken from the @zeit/next-mdx plugin source:
// https://github.com/zeit/next-plugins/blob/master/packages/next-mdx
module.exports = {
webpack: (config, {}) => {
config.module.rules.push({
test: /\.mdx/,
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: pluginOptions.options
}
]
})
return config
}
}
複製程式碼
自定義 babel 配置
Examples
為了擴充套件方便我們使用babel
,可以在應用根目錄新建.babelrc
檔案,該檔案可配置。
如果有該檔案,我們將會考慮資料來源,因此也需要定義 next 專案需要的東西,也就是 next/babel
預設。
這種設計方案將會使你不詫異於我們可以定製 babel 配置。
下面是.babelrc
檔案案例:
{
"presets": ["next/babel"],
"plugins": []
}
複製程式碼
next/babel
預設可處理各種 React 應用所需要的情況。包括:
- preset-env
- preset-react
- plugin-proposal-class-properties
- plugin-proposal-object-rest-spread
- plugin-transform-runtime
- styled-jsx
presets / plugins 不允許新增到.babelrc
中,然而你可以配置next/babel
預設:
{
"presets": [
["next/babel", {
"preset-env": {},
"transform-runtime": {},
"styled-jsx": {},
"class-properties": {}
}]
],
"plugins": []
}
複製程式碼
"preset-env"
模組選項應該保持為 false,否則 webpack 程式碼分割將被禁用。
暴露配置到服務端和客戶端
next/config
模組使你應用執行時可以讀取些儲存在next.config.js
的配置項。serverRuntimeConfig
屬性只在伺服器端可用,publicRuntimeConfig
屬性在服務端和客戶端可用。
// next.config.js
module.exports = {
serverRuntimeConfig: { // Will only be available on the server side
mySecret: 'secret'
},
publicRuntimeConfig: { // Will be available on both server and client
staticFolder: '/static',
mySecret: process.env.MY_SECRET // Pass through env variables
}
}
複製程式碼
// pages/index.js
import getConfig from 'next/config'
// Only holds serverRuntimeConfig and publicRuntimeConfig from next.config.js nothing else.
const {serverRuntimeConfig, publicRuntimeConfig} = getConfig()
console.log(serverRuntimeConfig.mySecret) // Will only be available on the server side
console.log(publicRuntimeConfig.staticFolder) // Will be available on both server and client
export default () => <div>
<img src={`${publicRuntimeConfig.staticFolder}/logo.png`} alt="logo" />
</div>
複製程式碼
啟動服務選擇 hostname
啟動開發環境服務可以設定不同的 hostname,你可以在啟動命令後面加上--hostname 主機名
或 -H 主機名
。它將會啟動一個 TCP 伺服器來監聽連線所提供的主機。
CDN 支援字首
建立一個 CDN,你能配置assetPrefix
選項,去配置你的 CDN 源。
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
// You may only need to add assetPrefix in the production.
assetPrefix: isProd ? 'https://cdn.mydomain.com' : ''
}
複製程式碼
注意:Next.js 執行時將會自動新增字首,但是對於/static
是沒有效果的,如果你想這些靜態資源也能使用 CDN,你需要自己新增字首。有一個方法可以判斷你的環境來加字首,如 in this example。
專案部署
部署中,你可以先構建打包生成環境程式碼,再啟動服務。因此,構建和啟動分為下面兩條命令:
next build
next start
複製程式碼
例如,使用now
去部署package.json
配置檔案如下:
{
"name": "my-app",
"dependencies": {
"next": "latest"
},
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
複製程式碼
然後就可以直接執行now
了。
Next.js 也有其他託管解決方案。請查考 wiki 章節'Deployment' 。
注意:NODE_ENV
可以通過next
命令配置,如果沒有配置,會最大渲染,如果你使用程式設計式寫法的話programmatically,你需要手動設定NODE_ENV=production
。
注意:推薦將.next
或自定義打包資料夾custom dist folder放入.gitignore
或 .npmignore
中。否則,使用files
或 now.files
新增部署白名單,並排除.next
或自定義打包資料夾。
瀏覽器支援
Next.js 支援 IE11 和所有的現代瀏覽器使用了@babel/preset-env
。為了支援 IE11,Next.js 需要全域性新增Promise
的 polyfill。有時你的程式碼或引入的其他 NPM 包的部分功能現代瀏覽器不支援,則需要用 polyfills 去實現。
ployflls 實現案例為polyfills。
匯出靜態頁面
Examples
next export
可以輸出一個 Next.js 應用作為靜態資源應用而不依靠 Node.js 服務。
這個輸出的應用幾乎支援 Next.js 的所有功能,包括動態路由,預獲取,預載入以及動態匯入。
next export
將把所有有可能渲染出的 HTML 都生成。這是基於對映物件的pathname
關鍵字關聯到頁面物件。這個對映叫做exportPathMap
。
頁面物件有2個屬性:
page
- 字串型別,頁面生成目錄query
- 物件型別,當預渲染時,query
物件將會傳入頁面的生命週期getInitialProps
中。預設為{}
。
使用
通常開發 Next.js 應用你將會執行:
next build
next export
複製程式碼
next export
命令預設不需要任何配置,將會自動生成預設exportPathMap
生成pages
目錄下的路由你頁面。
如果你想動態配置路由,可以在next.config.js
中新增非同步函式exportPathMap
。
// next.config.js
module.exports = {
exportPathMap: async function (defaultPathMap) {
return {
'/': { page: '/' },
'/about': { page: '/about' },
'/readme.md': { page: '/readme' },
'/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } },
'/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } },
'/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } }
}
}
}
複製程式碼
注意:如果 path 的結尾是目錄名,則將匯出
/dir-name/index.html
,但是如果結尾有副檔名,將會匯出對應的檔案,如上/readme.md
。如果你使用.html
以外的副檔名解析檔案時,你需要設定 header 的Content-Type
頭為"text/html".
輸入下面命令:
next build
next export
複製程式碼
你可以在package.json
新增一個 NPM 指令碼,如下所示:
{
"scripts": {
"build": "next build",
"export": "npm run build && next export"
}
}
複製程式碼
接著只用執行一次下面命令:
npm run export
複製程式碼
然後你將會有一個靜態頁面應用在out
目錄下。
你也可以自定義輸出目錄。可以執行
next export -h
命令檢視幫助。
現在你可以部署out
目錄到任意靜態資源伺服器上。注意如果部署 GitHub Pages 需要加個額外的步驟,文件如下
例如,訪問out
目錄並用下面命令部署應用ZEIT Now.
now
複製程式碼
限制
使用next export
,我們建立了個靜態 HTML 應用。構建時將會執行頁面裡生命週期getInitialProps
函式。
req
和res
只在服務端可用,不能通過getInitialProps
。
所以你不能預構建 HTML 檔案時動態渲染 HTML 頁面。如果你想動態渲染可以執行
next start
或其他自定義服務端 API。
多 zone
Examples
一個 zone 時一個單獨的 Next.js 應用。如果你有很多 zone,你可以合併成一個應用。
例如,你如下有兩個 zone:
- docs.my-app.com 服務於路由
/docs/**
- ui.my-app.com 服務於所有頁面
有多 zone 應用技術支援,你可以將幾個應用合併到一個,而且可以自定義 URL 路徑,使你能同時單獨開發各個應用。
與 microservices 觀念類似, 只是應用於前端應用.
怎麼定義一個 zone
zone 沒有單獨的 API 文件。你需要做下面事即可:
- 確保你的應用裡只有需要的頁面 (例如, ui.my-app.com 不包含
/docs/**
) - 確保你的應用有個字首assetPrefix。(你也可以定義動態字首dynamically)
怎麼合併他們
你能使用 HTTP 代理合並 zone
你能使用代理micro proxy來作為你的本地代理服務。它允許你定義路由規則如下:
{
"rules": [
{"pathname": "/docs**", "method":["GET", "POST", "OPTIONS"], "dest": "https://docs.my-app.com"},
{"pathname": "/**", "dest": "https://ui.my-app.com"}
]
}
複製程式碼
生產環境部署,如果你使用了ZEIT now,可以它的使用path alias 功能。否則,你可以設定你已使用的代理服務編寫上面規則來路由 HTML 頁面
技巧
- 設定301重定向
- 只處理伺服器端模組
- 構建專案 React-Material-UI-Next-Express-Mongoose-Mongodb
- 構建一個 SaaS 產品 React-Material-UI-Next-MobX-Express-Mongoose-MongoDB-TypeScript
問答
這個產品可以用於生產環境嗎?
它的開發體驗和終端使用者體驗都很好,所以我們決定開源出來給大家共享。
體積多大?
一個最簡單 Next 應該用 gzip 壓縮後大約65kb
這個像 `create-react-app`?
是,因為它讓你的 SSR 開發更簡單。
不是,因為它規定了一定的目錄結構,使我們能做以下更高階的事:
- 服務端渲染
- 自動程式碼分割
此外,Next.js 還提供兩個內建特性:
- 路由與懶載入元件:
<Link>
(通過引入next/link
) - 修改
<head>
的元件:<Head>
(通過引入next/head
)
如果你想寫共用元件,可以嵌入 Next.js 應用和 React 應用中,推薦使用create-react-app
。你可以更改import
保持程式碼清晰。
怎麼解決 CSS 嵌入 JS 問題?
哪些語法會被轉換?怎麼轉換它們?
為什麼使用新路由?
- 路由不需要被提前知道
- 路由總是被懶載入
- 頂層元件可以定義生命週期
getInitialProps
來阻止路由載入(當服務端渲染或路由懶載入時)
因此,我們可以介紹一個非常簡單的路由方法,它由下面兩部分組成:
- 每個頂層元件都將會收到一個
url
物件,來檢查 url 或修改歷史記錄 <Link />
元件用於包裝如(<a/>
)標籤的元素容器,來執行客戶端轉換。
我們使用了些有趣的場景來測試路由的靈活性,例如,可檢視nextgram。
我怎麼定義自定義路由?
在客戶端,我們<Link>
元件有個屬性as
,可以裝飾改變獲取到的 URL。
怎麼獲取資料?
我可以使用 GraphQL 嗎?
我可以使用 Redux 嗎?
我可以在 Next 應用中使用我喜歡的 Javascript 庫或工具包嗎?
什麼啟發我們做這個?
PHP 的易用性也是個很好的靈感來源,我們覺得 Next.js 可以替代很多需要用 PHP 輸出 HTML 的場景。
與 PHP 不同的是,我們得利於 ES6 模組系統,每個檔案會輸出一個元件或方法,以便可以輕鬆的匯入用於懶載入和測試
我們研究 React 的伺服器渲染時並沒有花費很大的步驟,因為我們發現一個類似於 Next.js 的產品,React 作者 Jordan Walke 寫的react-page (現在已經廢棄)
貢獻
可檢視 contributing.md
作者
- Arunoda Susiripala (@arunoda) – ZEIT
- Tim Neutkens (@timneutkens) – ZEIT
- Naoyuki Kanezawa (@nkzawa) – ZEIT
- Tony Kovanen (@tonykovanen) – ZEIT
- Guillermo Rauch (@rauchg) – ZEIT
- Dan Zajdband (@impronunciable) – Knight-Mozilla / Coral Project