Next.js,是一個用於 React 應用的極簡的伺服器端渲染框架。
這裡就節錄 Next.js Github 官網 v4.1.4 版本了,若要看原文版,請參考這裡:官網 README.md。言歸正傳,我們開始啦!
如何使用?
安裝
npm install --save next react react-dom
Next.js 4 只支持 React 16
由於 React 16 和 React 15 的工作方式與使用方法不同,所以 Next 不得不移除對 React 15 的支持
在你的 package.json 添加如下代碼:
"scripts": { "dev": "next", "build": "next build", "start": "next start" }
接下來,大部分事情都交由文件系統來處理。每個 .js 文件都變成了一個自訂處理和渲染的路由。
在項目中新建 ./page/index.js
export default () => <div>Welcome to next.js!</div>
然後,在控制台輸入 npm run dev
,打開 http://localhost:3000
即可看到程序已經運行,也可使用其他端口號: npm run dev -- -p 8080
。
目前為止,我們已經介紹了:
- 自動編譯和打包 (使用 webpack 和 babel)
- 代碼熱更新
- 以
./pages
目錄作為頁面渲染目錄的 server-side render - 靜態文件服務 (
./static/
被自動定位到/static/
)
代碼自動分割
你所聲明的每個 import 命令所導入的文件只會與相關頁面進行綁定並提供任務,也就是說,頁面不會加載不需要的代碼
import cowsay from 'cowsay-browser' export default () => <pre> {cowsay.say({text: 'hi there!'})} </pre>
CSS
嵌入式樣式 Build-in-CSS
我們提供 style-jsx 支持局部獨立作用域的 CSS(scope CSS),目的是提供一種類似於 web 組件的 shadow CSS,不過,後者並不支持 SSR (scope CSS 是支持的)
const Index = () => ( <div> Hello world <p>scoped!</p> <style jsx>{` p { color: red; } div { background: red; } @media (max=width: 600px) { div { background: blue; } } `}</style> <style global jsx>{` body { background: black; } `}</style> </div> ); export default Index;
- scope CSS 作用範圍,如果添加 jsx 屬性,則是不包括子組件的當前組件;如果添加了 global 和 jsx 屬性,則是包括了子組件在內的當前組件;如果沒添加任何屬性,則作用與添加了 global 和 jsx 的作用類似,只不過 next 不會對其進行額外的提取與優化打包。
- scope CSS 實現原理,其實就是在編譯好的代碼的對應元素上,添加一個以 jsx 開頭的 class,然後將對應的樣式代碼提取到此 class 下
內聯式樣式 CSS-in-JS
幾乎可使用所有內聯樣式解決方案:
export default () => <p style={{ 'color': 'red' }}>hi there</p>
為了使用更複雜的 CSS-in-JS 內聯樣式方案,你可能不得不在 SSR 的時候強制樣式刷新。我們透過允許自定義包裏著每個頁面的 <Document> 組件的方式來解決此問題。
靜態文件服務
在你項目根目錄新建 static 文件夾,然後你就可以在你的代碼透過 /static/ 開頭的路徑來引用此文夾的文件:
export default () => <img src="/static/my-img.png">
自定義 <head> 頭部元素
import Head from 'next/head'; export default () => <div> <Head> <title>My page title</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> </Head> <p>Hello world</p> </div>
注意:當組件卸載的時候,組件內定義的 <Head> 將會被清空,所以要確保每個頁面都在各自的 <Head> 內聲明了所有需要的內容,而不是假定這些東西已經在其他頁面中添加過了。
- next 框架自帶 <Head> 標籤,作為當前頁面的 <head>,如果在組件內自定義 <Head>,則自定義 <Head> 內的元素將會被追加到框架自帶的 <Head> 標籤中
- 每個組件自定義的 <Head> 內容只會應用在各自的頁面上,子組件內定義的 <Head> 也會追加到當前頁面的 <head> 內,如有重複定義的標籤或屬性,則子組件覆蓋父組件,位於文檔更後面的組件覆蓋更前面的組件
數據獲取及組件生命週期
你可透過導出一個基於 React.Component 的組件來獲取狀態 (state)、生命週期或者初始數據 (而不是無狀態函數 stateless)
import Head from 'next/head'; 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> <Head> <title>My page title</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> </Head> <p>Hello world {this.props.userAgent}</p> </div> ) } };
當頁面加載數據時,我們使用一個異步 (async) 的靜態方法 getInitialProps。此靜態方法能夠獲取所有的數據,並將其解析成一個 JavaScript 對象,然後將其作為屬性附加到 props 對象上。
當初始化頁面的時候,getInitialProps 只會在 server-side 執行,而當透過 Link 組件或使用命令路由 API 來將頁面導航到另外一個路由的時候,此方法就只會在客戶端執行。
注意: getInitialProps 不能 在子組件上使用,只能應用於當前頁面的頂層組件。
如果你在 getInitialProps 中引入了一些只能在 server-side 使用的模組 (例如 node.js 的核心模組),請確保透過正確的方式來導入它們 import them properly ,否則的話,那很可能會拖慢應用的速度。
你也可以為無狀態組件自定義 getInitialProps 生命週期方法:
import fetch from 'isomorphic-unfetch'; 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 部分,已被解析成一個 object
- asPath – 在瀏覽器上展示的實際路徑 (包括 query 字符串)
- req – HTTP request object (只存在 server-side)
- res – HTTP response object (只存在 server-side)
- jsonPageRes – 獲取的響應數據對象 Fetch Response (只存在於 client-side)
- err – 渲染時發生錯誤拋出的錯誤對象
基於 getInitialProps 在 server-side 和 client-side 的不同表現,例如 req 的存在與否,
可以透過此來區分 server-side 和 client-side。
路由 <Link>
可以透過 <Link> 組件來實現 client-side 在兩個路由間的切換功能,例如下面兩個頁面:
//pages/index.js import Link from 'next/link'; export default () => <div> Click{' '} <Link href="/about"> <a>here</a> </Link>{' '} to read more </div>
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 提供額外的配置項,當你在 server 上配置自定義路由的話,那麼此參數就會發揮作用。
as 可根據 server-side 路由的配置做出相對應的路由改變,例如,在 server-side,你自定義規定當獲取 /a 的 path 請求的時候,返回一個位於 /b 目錄下的頁面,則為了配合 server-side 的這種指定,你可以這麼定義 <Link> 組件:<Link href=”/a” as=”/b”><a>a</a></Link>
<Link> 組件主要用於路由跳轉功能,其可以接收一個必須的子元素 (DOM 標籤或純文字等)
- 如果添加的子元素是 DOM 元素,則 Link 會為此子元素賦予路由跳轉功能
- 如果添加的元素是純文字,則 <Link> 默認轉化為 a 標籤,包裏在此文字外部,如果當前組件有 jsx 屬性的 scope CSS,這個 a 標籤是不會受此 scope CSS 影響的,也就是說,不會加上以 jsx 開頭的類名
需要注意的是,直接添加純文字作為子元素的做法如今已經不被贊成 (deprecated)。
URL 對象
<Link> 組件可以接收一個 URL 對象,此 URL 將會被自動格式化為 URL 字符串。
import Link from 'next/link'; export default () => <div> Click{' '} <Link href={{pathname: '/about', query: {name: 'Zeit'}}} prefetch> <a>here</a> </Link>{' '} to read more </div>
上述代碼 <Link> 組件將會根據 href 屬性對象生成:/about?name=Zeit 的 URL 字符串,你也可以在此 URL 對象中使用 Node.js URL module document 中定義好的屬性來配置路由。
替換 (replace) 而非追加 (push) 路由 url
<Link> 組件默認將新的 URL 追加 (push) 到路由棧中,但你可使用 replace 屬性來避免此追加動作 (直接替換掉當前路由)。
import Link from 'next/link'; export default () => <div> Click{' '} <Link href="/about" replace> <a>here</a> </Link>{' '} to read more </div>
讓組件支持 onClick 事件
<Link> 標籤支持所有支持 onClick 事件的組件 (即只要某組件或元素標籤支持 onClick 事件,則 <Link> 就能夠為其提供跳轉路由的功能)。如果你沒給 <Link> 標籤添加一個 <a> 標籤的子元素的話,那麼它只會執行給定的 onClick 事件,而不是執行跳轉路由的動作。
import Link from 'next/link'; export default () => <div> Click{' '} <Link href="/about" replace> <img src="/static/image.png"/> </Link> </div>
將 <Link> 的 href 暴露給子元素 (child)
如果 <Link> 的子元素是一個 <a> 標籤並且沒有指定 href 屬性的話,那麼我們會自動指定此屬性 (與 <Link> 的 href 相同) 以避免重複工作,然而有時候,你可能想要透過一個被包裏在某個容器 (例如組件) 內的 <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:
- router – 當前路由字符串
- pathname – 不包括 query string 在內的當前路由的 path (也就是 pathname)
- query – Object with parsed query string. Defaults to {}
- asPath – 在瀏覽器地址欄顯示的當前頁面的實際地址 (包括 query 字符串)
- push(url, as=url) – 透過 pushState 來跳轉路由到給定的 url
- replace(url, as=url) – 透過 replaceState 來將當前路由替換到給定的路由地址 url 上
push 和 replace 的第二個參數 as 提供額外的配置項,當你在 server 上配置自定義路由的話,那麼此參數就會發揮作用。
為了使編程的方式而不是觸發導航和組件獲取方式來切換路由,可在組件內部使用 props.url.push 和 props.url.replace
除非特殊需要,否則在組件不贊成 deprecated 使用 props.url.push 和 props.url.replace,而是建議使用 next/router 的相關 API。
URL 對象
命令式路由 (next/router) 所接收的 URL 對象與 <Link> 的 URL 對象很類似,你可以使用相同的方式來 push 和 replace 路由 URL:
import Router from 'next/router'; const handler = () => Router.push({ pathname: '/about', query: {name: 'Zeit'} }); export default () => <div> Click <span onClick={handler}>here</span> to read more </div>
命令式路由 (next/router) 的 URL 對象的屬性及其參數的使用方法和 <Link> 組件完全一樣。
路由事件
你還可以監聽到與 Router 相關的一些事件。
以下是你所能夠監聽的 Router 事件:
- routeChangeStart(url) – 當路由剛開始切換的時候觸發
- routeChangeComplete(url) – 當路由切換完成時觸發
- routeChangeError(err, url) – 當路由切換發生錯誤時觸發
- beforeHistoryChange(url) – 在改變瀏覽器 history 之前觸發
- appUpdated(nextRoute) – 當切換頁面的時候,應用版本剛好更新的時觸發 (例如在部署期間切換路由)
上面 API 中的 url 參數指的是瀏覽器地址欄顯示的鏈接地址,如果你使用 Router.push(url, as) (或者類似的方法) 來改變路由,則此值就將是 as 的值
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 方式進行導航),我們已經自動幫你做了這些事。
不過,你也可以透過 Router.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 方法
使用高階函數 HOC
如果你想在應用的任何組件都能獲取到 router 對象,那麼你可使用 withRouter 高階函數,下面是一個使用此高階函數的示例:
import {withRouter} from 'next/router'; const ActiveLink = ({children, router, href}) => { const style = { marginRight: 10, color: router.pathname === href ? 'red' : 'black' }; const handleClick = e => { e.preventDefault(); router.push(href); }; return ( <a href={href} onClick={handleClick} style={style}> {children} </a> ); }; export default withRouter(ActiveLink);
上述代碼中的 router 對象擁有和 next/router 相同的 API。
預獲取頁面 Prefetching Pages
Next.js 自帶允許你預獲取 (prefetch) 頁面的 API
因為 Next.js 在 Server-side 渲染頁面,所以應用的所有將來可能發生交互的相關鏈接可以在瞬間完成交互,事實上 Next.js 可以透過預下載功能來達到一個絕佳的加載性能
由於 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 router transition will happen after 100ms </a> { // but we can prefetch it! Router.prefetch('/dynamic') } </div>
自定義 Server 和路由
一般來說,你可以使用 next start 命令啟動 next 服務,但是,你也完全可以使用編程 (programmatically) 的方式,例如路由匹配等,來定製化路由。
下面是一個將 /a 匹配到 ./page/b,以及將 /b 匹配到 ./page/a 的例子:
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) => { 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) 隱藏包括 server-side 消息在內的錯誤消息 – 默認為 false
- conf (object) 和 next.config.js 中的對象是同一個 – 默認為 false
然後,將你的 start 命令改寫成: NODE_ENV=production node server.js
異步導入 Dynamic Import
Next.js 支持 JavaScript TC39 的 dynamic import proposal 規範,所以你可以動態載入 (import) JavaScript 模組 (例如 React Component)
你可以將動態導入理解為一種將代碼分割為更易管理和理解的方式。
由於 Next.js 支持 server-side render 的動態導入,所以你可以用它來做一些酷炫的東西。
- 基本用法 (同樣支持 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/hello2'), { 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/_document.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 等一些 Server-side Render 容器
注意:<Main /> 之外的 React 組件都不會被瀏覽器初始化,如果你想在所有頁面中使用某些組件 (例如菜單欄或工具欄),首先保證不要在其中添加有關應用邏輯的內容,可看這個例子
不會被初始化和執行的邏輯代碼包括除了 render 之外的所有生命週期鉤子函數,例如:componentDidMount、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
自定義配置
為了對 Next.js 進行更複雜的自定義操作,你可在項目的根目錄下新建一個 next.config.js 文件
注意:next.config.js 是一個標準的 Node.js 模組,而不是一個 JSON 文件,此文件在 Next 項目的 Server 端以及 build 階段會被調用,但是在瀏覽器端構建時是不會起作用的。
module.exports = { /* config options here */ }
設置一個自定義的構建 (build) 目錄
你可以自行指定構建打包的輸出目錄,例如下面範例將默認打包輸出目錄 .next 指定為 build 目錄:
module.exports = { distDir: 'build' }
Configuring the onDemandEntries
Next 暴露了一些能夠讓你自己控制如何部署服務或者緩存頁面的配置:
module.exports = { onDemandEntries: { // 控制頁面內存 `buffer` 中緩存的時間,單位是 ms maxInactiveAge: 25 * 1000, // number of pages that should be kept simultaneously without being disposed pagesBufferLength: 2, } };
自定義 webpack 配置
你可以透過 next.conf.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 配置
為了擴展對 Babel 的使用,你可以在應用的根目錄下新建 .babelrc 文件,此文件是非必需的
如果此文件存在,那麼我們就認為這個才是真正的 Babel 配置文件,因此也就需要為其定義一些 next 項目需要的東西,並將之當作是 next/babel 的預設配置 (preset) ,這種設計是為了避免你有可能對我們定制 babel 配置而感到訝異。
下面是一個 .babelrc 文件的示例:
{ "presets": ["next/babel", "stage-0"] }
CDN 支持
你可以設定 assetPrefix 項來配置 CDN 源,以便能夠與 Next.js 項目的 host 保持對應。
const isProd = process.env.NODE_ENV === 'production' module.exports = { // You may only need to add assetPrefix in the production. assetPrefix: isProd ? 'https://cdn.mydomain.com' : '' }
注意:Next.js 將會自動使用所加載腳本的 CDN 域 (作為項目的 CDN 域),但是對 /static 目錄下的靜態文件就無能為力了。如果你想讓那些靜態文件也能用上 CDN,那你就不得不要自己指定 CDN 域,有種方法可讓你的項目自動根據運行環境來確定 CDN 域,可看看這個例子
項目部署
構建打包和啟動項目被分成了以下兩條命令:
next build next start
例如,你可像下面這樣為 now 項目配置 package.json 文件:
{ "name": "my-app", "dependencies": { "next": "latest" }, "scripts": { "dev": "next", "build": "next build", "start": "next start" } }
然後就可直接啟動 now 項目了!
Next.js 也可使用其他的託管方案,更多詳細可看下這部分內容 Deployment
注意:我們推薦你推送 .next,或者你自定義的打包輸出目錄 (到託管方案上) (Custom Config,可自定義一個專門用於放置配置文件(例如 .npmignore or .gitignore) 的文件夾。否則的話,使用 files or now.files 來選擇要部署的白名單(很明顯要排除掉 .next 或你自定義的打包輸出目錄))
導出靜態 HTML 頁面
你可將你的 Next.js 應用當成一個不依賴於 Node.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 則是不可用的 (req 和 res 只在服務器端可用)
基於此,你也無法在我們預先構建 HTML 文件的時候,動態的呈現 HTML 頁面,如果你真的想要這麼做,請使用 next start
相關技巧
- 301 Redirects with Next.js
- SSR and Server Only Modules
- Next.js + CSS
- Apollo Example
- Redux Example
- More Other Example