最近想稍稍看下 React
的 SSR
框架 Next.js,由于不想看二手资料, 因此本身跑到 Github上看,Next.js
的文档是英文的,看却是大概也能看得懂, 但有些地方不太肯定,并且英文看着毕竟不太爽你懂得,因此在网上搜了几圈发现好像好像尚未中文翻译,想着长痛不如短痛, 索性一边看一边翻译,本身翻译的东西本身看得也爽,不过毕竟能力有限,有些地方我也不知道该怎么翻译才好,因此翻译得不太通畅, 或者有几句干脆不翻译了。css
so,各位如果以为我哪点翻译得不太准确,或者对于那几句我没翻译的地方有更好的看法,欢迎提出~html
如下是全文翻译的 Next.js的 README.md文件,版本是 v4.1.4
,除了翻译原文以外,还加了一点我的小小看法。node
另外,没太弄明白掘金写文章的md
页面内超连接的语法是什么,因此下面的目录超连接没有效果,不过不影响阅读,想要更好的阅读体验能够去个人 github上看,别忘了 star
哦~react
Next.js是一个用于React应用的极简的服务端渲染框架。webpack
请访问 learnnextjs.com 以获取更多详细内容.git
安装方法:github
npm install --save next react react-dom
复制代码
Next.js 4
只支持 React 16.
因为React 16
和React 15
的工做方式以及使用方法不尽相同,因此咱们不得不移除了对React 15
的支持web
在你的 package.json
文件中添加以下代码:express
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
复制代码
接下来,大部分事情都交由文件系统来处理。每一个 .js
文件都变成了一个自动处理和渲染的路由。npm
在项目中新建 ./pages/index.js
:
export default () => <div>Welcome to next.js!</div>
复制代码
而后,在控制台输入 npm run dev
命令,打开 http://localhost:3000
便可看到程序已经运行,你固然也可使用其余的端口号,可使用这条命令:npm run dev -- -p <your port here>
。
目前为止,咱们已经介绍了:
webpack
和 babel
)./pages
目录做为页面渲染目录的的服务器端渲染./static/
被自动定位到 /static/
)想要亲自试试这些到底有多简单, check out
sample app - nextgram
你所声明的每一个 import
命令所导入的文件会只会与相关页面进行绑定并提供服务,也就是说,页面不会加载不须要的代码。
import cowsay from 'cowsay-browser'
export default () =>
<pre> {cowsay.say({ text: 'hi there!' })} </pre>
复制代码
咱们提供 style-jsx来支持局部独立做用域的 CSS
(scope CSS
),目的是提供一种相似于 Web
组件的 shadow CSS
,不过,后者(即shadow CSS
)并不支持服务器端渲染(scope CSS
是支持的)。
export default () =>
<div> Hello world <p>scoped!</p> <style jsx>{` p { color: blue; } div { background: red; } @media (max-width: 600px) { div { background: blue; } } `}</style> <style global jsx>{` body { background: black; } `}</style> </div>
复制代码
更多示例可见 styled-jsx documentation
译者注:
scope CSS
的做用范围,若是添加了jsx
属性,则是不包括子组件的当前组件;若是添加了global
和jsx
属性,则是包括了子组件在内的当前组件;若是没添加任何属性,则做用与 添加了global
和jsx
的做用相似,只不过next
不会对其进行额外的提取与优化打包scope CSS
的实现原理,其实就是在编译好的代码的对应元素上,添加一个以jsx
开头的类名(class
),而后将对应的样式代码提取到此类名下
几乎可使用全部的内联样式解决方案,最简单一种以下:
export default () => <p style={{ color: 'red' }}>hi there</p>
复制代码
为了使用更多复杂的 CSS-in-JS
内联样式方案,你可能不得不在服务器端渲染的时候强制样式刷新。咱们经过容许自定义包裹着每一个页面的 <Document>
组件的方式来解决此问题。
在你的项目的根目录新建 static
文件夹,而后你就能够在你的代码经过 /static/
开头的路径来引用此文件夹下的文件:
export default () => <img src="/static/my-image.png" /> 复制代码
<head>
头部元素咱们暴露了一个用于将元素追加到 <head>
中的组件。
import Head from 'next/head'
export default () =>
<div> <Head> <title>My page title</title> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> </Head> <p>Hello world!</p> </div> 复制代码
注意:当组件卸载的时候,组件内定义的 <Head>
将会被清空,因此请确保每一个页面都在其各自的 <Head>
内声明了其全部须要的内容,而不是假定这些东西已经在其余页面中添加过了。
译者注:
next
框架自带<head>
标签,做为当前页面的<head>
,若是在组件内自定义了<Head>
,则自定义<Head>
内的元素(例如<title>
、<meta>
等)将会被追加到框架自带的<head>
标签中- 每一个组件自定义的
<Head>
内容只会应用在各自的页面上,子组件内定义的<Head>
也会追加到当前页面的<head>
内,若是有重复定义的标签或属性,则子组件覆盖父组件,位于文档更后面的组件覆盖更前面的组件。
你能够经过导出一个基于 React.Component
的组件来获取状态(state
)、生命周期或者初始数据(而不是一个无状态函数(stateless
),就像上面的一段代码)
import React from 'react'
export default class extends React.Component {
static async getInitialProps({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
return { userAgent }
}
render() {
return (
<div> Hello World {this.props.userAgent} </div>
)
}
}
复制代码
你可能已经注意到了,当加载页面获取数据的时候,咱们使用了一个异步(async
)的静态方法 getInitialProps
。此静态方法可以获取全部的数据,并将其解析成一个 JavaScript
对象,而后将其做为属性附加到 props
对象上。
当初始化页面的时候,getInitialProps
只会在服务器端执行,而当经过 Link
组件或者使用命令路由 API
来将页面导航到另一个路由的时候,此方法就只会在客户端执行。
注意:getInitialProps
不能 在子组件上使用,只能应用于当前页面的顶层组件。
若是你在
getInitialProps
中引入了一些只能在服务器端使用的模块(例如一些node.js
的核心模块),请确保经过正确的方式来导入它们 import them properly,不然的话,那极可能会拖慢应用的速度。
你也能够为无状态(stateless
)组件自定义 getInitialProps
生命周期方法:
const Page = ({ stars }) =>
<div>
Next stars: {stars}
</div>
Page.getInitialProps = async ({ req }) => {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
export default Page
复制代码
getInitialProps
接收的上下文对象包含如下属性:
pathname
- URL
的 path
部分query
- URL
的 query string
部分,而且其已经被解析成了一个对象asPath
- 在浏览器上展现的实际路径(包括 query
字符串)req
- HTTP request
对象 (只存在于服务器端)res
- HTTP response
对象 (只存在于服务器端)jsonPageRes
- 获取的响应数据对象 Fetch Response (只存在于客户端)err
- 渲染时发生错误抛出的错误对象译者注: 基于
getInitialProps
在服务器端和客户端的不一样表现,例如req
的存在与否,能够经过此来区分服务器端和客户端。
<Link>
能够经过 <Link>
组件来实现客户端在两个路由间的切换功能,例以下面两个页面:
// pages/index.js
import Link from 'next/link'
export default () =>
<div> Click{' '} <Link href="/about"> <a>here</a> </Link>{' '} to read more </div>
复制代码
// pages/about.js
export default () => <p>Welcome to About!</p>
复制代码
注意:可使用 <Link prefetch>
来让页面在后台同时获取和预加载,以得到最佳的页面加载性能
客户端路由行为与浏览器彻底相同:
getInitialProps
,那么进行数据的获取,若是抛出异常,则将渲染_error.js
1
和步骤2
完成后,pushState
开始执行,接着新组件将会被渲染每个顶层组件都会接收到一个 url
属性,其包括了如下 API
:
pathname
- 不包括 query
字符串在内的当前连接地址的 path
字符串(即pathname
)query
- 当前连接地址的 query
字符串,已经被解析为对象,默认为 {}
asPath
- 在浏览器地址栏显示的当前页面的实际地址(包括 query
字符串)push(url, as=url)
- 经过 pushState
来跳转路由到给定的 url
replace(url, as=url)
- 经过 replaceState
来将当前路由替换到给定的路由地址 url
上push
以及 replace
的第二个参数 as
提供了额外的配置项,当你在服务器上配置了自定义路由的话,那么此参数就会发挥做用。
译者注1: 上面那句话的意思是,
as
能够根据服务器端路由的配置做出相应的 路由改变,例如,在服务器端,你自定义规定当获取/a
的path
请求的时候,返回一个位于/b
目录下的页面,则为了配合服务器端的这种指定,你能够这么定义<Link/>
组件:<Link href='/a' as='/b'><a>a</a></Link>
这种作法有一个好处,那就是尽管你将 /a请求指定到了 /b页面,可是由于as的值为 /a,因此编译后的 DOM元素显示的连接的 href值为 /a,可是当真正点击连接时,响应的真正页面仍是 /b
译者注2:
<Link>
组件主要用于路由跳转功能,其能够接收一个必须的子元素(DOM
标签或者纯文字等)
- 若是添加的子元素是
DOM
元素,则Link
会为此子元素赋予路由跳转功能;- 若是添加的元素是纯文字,则
<Link>
默认转化为a
标签,包裹在此文字外部(即做为文字的父元素),若是当前组件有jsx
属性的scope CSS
,这个a
标签是不会受此scope CSS
影响的,也就是说,不会加上以jsx
开头的类名。
须要注意的是,直接添加纯文字做为子元素的作法现在已经不被同意了(deprecated)。
<Link>
组件能够接收一个 URL
对象,此 URL
对象将会被自动格式化为 URL
字符串。
// pages/index.js
import Link from 'next/link'
export default () =>
<div> Click{' '} <Link href={{ pathname: '/about', query: { name: 'Zeit' } }}> <a>here</a> </Link>{' '} to read more </div>
复制代码
上述代码中 <Link>
组件的将会根据 href
属性的对象值生成一个 /about?name=Zeit
的 URL
字符串,你也能够在此 URL
对象中使用任何已经在 Node.js URL module documentation 中定义好了的属性来配置路由。
replace
)而非追加(push
)路由 url
<Link>
组件默认将新的 URL
追加 (push
)到路由栈中,但你可使用 replace
属性来避免此追加动做(直接替换掉当前路由)。
// pages/index.js
import Link from 'next/link'
export default () =>
<div> Click{' '} <Link href="/about" replace> <a>here</a> </Link>{' '} to read more </div>
复制代码
onClick
事件<Link>
supports any component that supports the onClick
event. In case you don't provide an <a>
tag, it will only add the onClick
event handler and won't pass the href
property. <Link>
标签支持全部支持 onClick
事件的组件(即只要某组件或者元素标签支持 onClick
事件,则 <Link>
就可以为其提供跳转路由的功能)。若是你没有给 <Link>
标签添加一个 <a>
标签的子元素的话,那么它只会执行给定的 onClick
事件,而不是执行跳转路由的动做。
// pages/index.js
import Link from 'next/link'
export default () =>
<div> Click{' '} <Link href="/about"> <img src="/static/image.png" /> </Link> </div> 复制代码
<Link>
的 href
暴露给其子元素(child
)若是 <Link>
的子元素是一个 <a>
标签而且没有指定 href
属性的话,那么咱们会自动指定此属性(与 <Link>
的 herf
相同)以免重复工做,然而有时候,你可能想要经过一个被包裹在某个容器(例如组件)内的 <a>
标签来实现跳转功能,可是 Link
并不认为那是一个超连接,所以,就不会把它的 href
属性传递给子元素,为了不此问题,你应该给 Link
附加一个 passHref
属性,强制让 Link
将其 href
属性传递给它的子元素。
import Link from 'next/link'
import Unexpected_A from 'third-library'
export default ({ href, name }) =>
<Link href={href} passHref> <Unexpected_A> {name} </Unexpected_A> </Link>
复制代码
你可使用 next/router
来实现客户端侧的页面切换
import Router from 'next/router'
export default () =>
<div> Click <span onClick={() => Router.push('/about')}>here</span> to read more </div>
复制代码
上述代码中的 Router
对象拥有如下 API
:
route
- 当前路由字符串pathname
- 不包括查询字符串(query string
)在内的当前路由的 path
(也就是 pathname
)query
- Object
with the parsed query string. Defaults to {}
asPath
- 在浏览器地址栏显示的当前页面的实际地址(包括 query
字符串)push(url, as=url)
- 经过 pushState
来跳转路由到给定的 url
replace(url, as=url)
- 经过 replaceState
来将当前路由替换到给定的路由地址 url
上push
以及 replace
的第二个参数 as
提供了额外的配置项,当你在服务器上配置了自定义路由的话,那么此参数就会发挥做用。
为了使用编程的方式而不是触发导航和组件获取的方式来切换路由,能够在组件内部使用 props.url.push
和 props.url.replace
译者注: 除非特殊须要,不然在组件内部不同意(deprecated)使用
props.url.push
和props.url.replace
,而是建议使用next/router
的相关API
。
命令式路由 (next/router
)所接收的 URL
对象与 <Link>
的 URL
对象很相似,你可使用相同的方式来push
和 replace
路由URL
import Router from 'next/router'
const handler = () =>
Router.push({
pathname: '/about',
query: { name: 'Zeit' }
})
export default () =>
<div> Click <span onClick={handler}>here</span> to read more </div>
复制代码
命令式路由 (next/router
)的 URL
对象的属性及其参数的使用方法和 <Link>
组件的彻底同样。
你还能够监听到与 Router
相关的一些事件。
如下是你所可以监听的 Router
事件:
routeChangeStart(url)
- 当路由刚开始切换的时候触发routeChangeComplete(url)
- 当路由切换完成时触发routeChangeError(err, url)
- 当路由切换发生错误时触发beforeHistoryChange(url)
- 在改变浏览器 history
以前触发appUpdated(nextRoute)
- 当切换页面的时候,应用版本恰好更新的时触发(例如在部署期间切换路由)Here
url
is the URL shown in the browser. If you callRouter.push(url, as)
(or similar), then the value ofurl
will beas
. 上面API
中的url
参数指的是浏览器地址栏显示的连接地址,若是你使用Router.push(url, as)
(或者相似的方法)来改变路由,则此值就将是as
的值
下面是一段如何正确地监听路由事件 routeChangeStart
的示例代码:
Router.onRouteChangeStart = url => {
console.log('App is changing to: ', url)
}
复制代码
若是你不想继续监听此事件了,那么你也能够很轻松地卸载掉此监听事件,就像下面这样:
Router.onRouteChangeStart = null
复制代码
若是某个路由加载被取消掉了(例如连续快速地单击两个连接),routeChangeError
将会被执行。此方法的第一个参数 err
对象中将包括一个值为 true
的 cancelled
属性。
Router.onRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
}
复制代码
若是你在一次项目新部署的过程当中改变了路由,那么咱们就没法在客户端对应用进行导航,必需要进行一次完整的导航动做(译者注:意思是没法像正常那样经过 PWA
的方式进行导航),咱们已经自动帮你作了这些事。 不过,你也能够经过 Route.onAppUpdated
事件对此进行自定义操做,就像下面这样:
Router.onAppUpdated = nextUrl => {
// persist the local state
location.href = nextUrl
}
复制代码
译者注:
通常状况下,上述路由事件的发生顺序以下:
routeChangeStart
beforeHistoryChange
routeChangeComplete
浅层路由(Shallow routing
)容许你在不触发 getInitialProps
的状况下改变路由(URL
),你能够经过要加载页面的 url
来获取更新后的 pathname
和 query
,这样就不会丢失路由状态(state
)了。
你能够经过调用 Router.push
或 Router.replace
,并给它们加上 shallow: true
的配置参数来实现此功能,下面是一个使用示例:
// Current URL is "/"
const href = '/?counter=10'
const as = href
Router.push(href, as, { shallow: true })
复制代码
如今,URL
已经被更新到了 /?counter=10
,你能够在组件内部经过 this.props.url
来获取此 URL
你能够在 componentWillReceiveProps
钩子函数中获取到 URL
的变化,就像下面这样:
componentWillReceiveProps(nextProps) {
const { pathname, query } = nextProps.url
// fetch data based on the new query
}
复制代码
注意:
浅层路由只会在某些页面上起做用,例如,咱们能够假定存在另一个名为
about
的页面,而后执行下面这行代码:Router.push('/about?counter=10', '/about?counter=10', { shallow: true }) 复制代码
由于这是一个新的页面(
/about?counter=10
),因此即便咱们已经声明了只执行浅层路由,但当前页面仍然会被卸载掉(unload
),而后加载这个新的页面并调用getInitialProps
方法
若是你想在应用的任何组件都能获取到 router
对象,那么你可使用 withRouter
高阶函数,下面是一个使用此高阶函数的示例:
import { withRouter } from 'next/router'
const ActiveLink = ({ children, router, href }) => {
const style = {
marginRight: 10,
color: router.pathname === href? 'red' : 'black'
}
const handleClick = (e) => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}> {children} </a>
)
}
export default withRouter(ActiveLink)
复制代码
上述代码中的 router
对象拥有和 next/router
相同的 API
。
(下面就是一个小例子)
Next.js
自带容许你预获取(prefetch
)页面的 API
由于 Next.js
在服务器端渲染页面,因此应用的全部未来可能发生交互的相关连接路径能够在瞬间完成交互,事实上 Next.js
能够经过预下载功能来达到一个绝佳的加载性能。[更多详细可见](Read more.)
因为
Next.js
只会预加载JS
代码,因此在页面加载的时候,你能够还须要花点时间来等待数据的获取。
<Link>
组件你能够为任何一个 <Link>
组件添加 prefetch
属性,Next.js
将会在后台预加载这些页面。
import Link from 'next/link'
// example header component
export default () =>
<nav> <ul> <li> <Link prefetch href="/"> <a>Home</a> </Link> </li> <li> <Link prefetch href="/about"> <a>About</a> </Link> </li> <li> <Link prefetch href="/contact"> <a>Contact</a> </Link> </li> </ul> </nav>
复制代码
大部分预获取功能都须要经过 <Link>
组件来指定连接地址,可是咱们还暴露了一个命令式的 API
以方便更加复杂的场景:
import Router from 'next/router'
export default ({ url }) =>
<div> <a onClick={() => setTimeout(() => url.pushTo('/dynamic'), 100)}> A route transition will happen after 100ms </a> {// but we can prefetch it! Router.prefetch('/dynamic')} </div>
复制代码
通常来讲,你可使用 next start
命令启动 next
服务,可是,你也彻底可使用编程(programmatically
)的方式,例如路由匹配等,来定制化路由。
下面就是一个将 /a
匹配到 ./page/b
,以及将 /b
匹配到 ./page/a
的例子:
// This file doesn't not go through babel or webpack transformation.
// Make sure the syntax and sources this file requires are compatible with the current node version you are running
// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
复制代码
next API
以下所示:
next(path: string, opts: object)
- path
是 Next
应用当前的路由位置next(opts: object)
上述 API
中的 opt
对象存在以下属性:
dev
(bool
) 是否使用开发模式(dev
)来启动 Next.js
- 默认为 false
dir
(string
) 当前 Next
应用的路由位置 - 默认为 '.'
quiet
(bool
) 隐藏包括服务器端消息在内的错误消息 - 默认为 false
conf
(object
) 和next.config.js
中的对象是同一个 - 默认为 {}
而后,将你(在 package.json
中配置)的 start
命令(script
)改写成 NODE_ENV=production node server.js
。
Next.js
支持 JavaScript TC39
的dynamic import proposal规范,因此你能够动态导入(import
) JavaScript
模块(例如 React Component
)。
你能够将动态导入理解为一种将代码分割为更易管理和理解的方式。 因为 Next.js
支持服务器端渲染侧(SSR
)的动态导入,因此你能够用它来作一些炫酷的东西。
SSR
)import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(import('../components/hello'))
export default () =>
<div> <Header /> <DynamicComponent /> <p>HOME PAGE is here!</p> </div>
复制代码
import dynamic from 'next/dynamic'
const DynamicComponentWithCustomLoading = dynamic(
import('../components/hello2'),
{
loading: () => <p>...</p>
}
)
export default () =>
<div>
<Header />
<DynamicComponentWithCustomLoading />
<p>HOME PAGE is here!</p>
</div>
复制代码
SSR
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(import('../components/hello3'), {
ssr: false
})
export default () =>
<div> <Header /> <DynamicComponentWithNoSSR /> <p>HOME PAGE is here!</p> </div>
复制代码
import dynamic from 'next/dynamic'
const HelloBundle = dynamic({
modules: props => {
const components = {
Hello1: import('../components/hello1'),
Hello2: import('../components/hello2')
}
// Add remove components based on props
return components
},
render: (props, { Hello1, Hello2 }) =>
<div>
<h1> {props.title} </h1>
<Hello1 />
<Hello2 /> </div>
})
export default () => <HelloBundle title="Dynamic Bundle" /> 复制代码
<Document>
Next.js
帮你自动跳过了在为页面添加文档标记元素的操做,例如, 你历来不须要主动添加 <html>
、<body>
这些文档元素。若是你想重定义这些默认操做的话,那么你能够建立(或覆写) ./page/_ducument.js
文件,在此文件中,对 Document
进行扩展:
// ./pages/_document.js
import Document, { Head, Main, NextScript } from 'next/document'
import flush from 'styled-jsx/server'
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
const { html, head, errorHtml, chunks } = renderPage()
const styles = flush()
return { html, head, errorHtml, chunks, styles }
}
render() {
return (
<html> <Head> <style>{`body { margin: 0 } /* custom! */`}</style> </Head> <body className="custom_class"> {this.props.customValue} <Main /> <NextScript /> </body> </html>
)
}
}
复制代码
在如下前提下,全部的 getInitialProps
钩子函数接收到的 ctx
都指的是同一个对象:
renderPage
(Function
)是真正执行 React
渲染逻辑的函数(同步地),这种作法有助于此函数支持一些相似于 Aphrodite's
的 renderStatic
等一些服务器端渲染容器。注意:<Main/>
以外的 React
组件都不会被浏览器初始化,若是你想在全部的页面中使用某些组件(例如菜单栏或者工具栏),首先保证不要在其中添加有关应用逻辑的内容,而后能够看看这个例子
译者注: 上面那句话的意思是,在
_document.js
文件中,你能够额外添加其余的一些组件,可是这全部的组件中,除了<Main/>
之外,其余的组件内的全部逻辑都不会被初始化和执行,这些不会被初始化和执行的逻辑代码包括除了render
以外的全部生命周期钩子函数,例如componnetDidMount
、componentWillUpdate
,以及一些监听函数,例如onClick
、onMouseOver
等,因此若是你要在_document.js
添加额外的组件,请确保这些组件中除了render
以外没有其余的逻辑
客户端和服务器端都会捕获并使用默认组件 error.js
来处理 404
和 500
错误。若是你但愿自定义错误处理,能够对其进行覆写:
import React from 'react'
export default class Error extends React.Component {
static getInitialProps({ res, jsonPageRes }) {
const statusCode = res
? res.statusCode
: jsonPageRes ? jsonPageRes.status : null
return { statusCode }
}
render() {
return (
<p> {this.props.statusCode ? `An error ${this.props.statusCode} occurred on server` : 'An error occurred on client'} </p>
)
}
}
复制代码
若是你想使用内置的错误页面,那么你能够经过 next/error
来实现:
import React from 'react'
import Error from 'next/error'
import fetch from 'isomorphic-fetch'
export default class Page extends React.Component {
static async getInitialProps() {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const statusCode = res.statusCode > 200 ? res.statusCode : false
const json = await res.json()
return { statusCode, stars: json.stargazers_count }
}
render() {
if (this.props.statusCode) {
return <Error statusCode={this.props.statusCode} /> } return ( <div> Next stars: {this.props.stars} </div> ) } } 复制代码
若是你想使用自定义的错误页面,那么你能够导入你本身的错误(
_error
)页面组件而非内置的next/error
译者注: 若是你只是想覆写默认的错误页面,那么能够在
/pages
下新建一个名为_error.js
的文件,Next
将使用此文件来覆盖默认的错误页面
为了对 Next.js
进行更复杂的自定义操做,你能够在项目的根目录下(和 pages/
以及 package.json
属于同一层级)新建一个 next.config.js
文件
注意:next.confgi.js
是一个标准的 Node.js
模块,而不是一个 JSON
文件,此文件在 Next
项目的服务端以及 build
阶段会被调用,可是在浏览器端构建时是不会起做用的。
// next.config.js
module.exports = {
/* config options here */
}
复制代码
build
)目录你能够自行指定构建打包的输出目录,例如,下面的配置将会建立一个 build
目录而不是 .next
做为构建打包的输出目录,若是没有特别指定的话,那么默认就是 .next
// next.config.js
module.exports = {
distDir: 'build'
}
复制代码
Next
暴露了一些可以让你本身控制如何部署服务或者缓存页面的配置:
module.exports = {
onDemandEntries: {
// 控制页面在内存`buffer`中缓存的时间,单位是 ms
maxInactiveAge: 25 * 1000,
// number of pages that should be kept simultaneously without being disposed
pagesBufferLength: 2,
}
}
复制代码
你能够经过 next.config.js
中的函数来扩展 webpack
的配置
// This file is not going through babel transformation.
// So, we write it in vanilla JS
// (But you could use ES2015 features supported by your Node.js version)
module.exports = {
webpack: (config, { buildId, dev }) => {
// Perform customizations to webpack config
// Important: return the modified config
return config
},
webpackDevMiddleware: config => {
// Perform customizations to webpack dev middleware config
// Important: return the modified config
return config
}
}
复制代码
警告:不推荐在 webpack
的配置中添加一个支持新文件类型(css less svg
等)的 loader
,由于 webpack
只会打包客户端代码,因此(loader
)不会在服务器端的初始化渲染中起做用。Babel
是一个很好的替代品,由于其给服务器端和客户端提供一致的功能效果(例如,babel-plugin-inline-react-svg)。
为了扩展对 Babel
的使用,你能够在应用的根目录下新建 .babelrc
文件,此文件是非必须的。 若是此文件存在,那么咱们就认为这个才是真正的Babel
配置文件,所以也就须要为其定义一些 next
项目须要的东西, 并将之当作是next/babel
的预设配置(preset
) 这种设计是为了不你有可能对咱们可以定制 babel
配置而感到诧异。
下面是一个 .babelrc
文件的示例:
{
"presets": ["next/babel", "stage-0"]
}
复制代码
你能够设定 assetPrefix
项来配置 CDN
源,以便可以与 Next.js
项目的 host
保持对应。
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
// You may only need to add assetPrefix in the production.
assetPrefix: isProd ? 'https://cdn.mydomain.com' : ''
}
复制代码
注意:Next.js
将会自动使用所加载脚本的 CDN
域(做为项目的 CDN
域),可是对 /static
目录下的静态文件就无能为力了。若是你想让那些静态文件也能用上CDN
,那你就不得不要本身指定 CDN
域,有种方法也可让你的项目自动根据运行环境来肯定 CDN
域,能够看看这个例子
构建打包和启动项目被分红了如下两条命令:
next build
next start
复制代码
例如,你能够像下面这样为 now
项目配置 package.json
文件:
{
"name": "my-app",
"dependencies": {
"next": "latest"
},
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
复制代码
而后就能够直接启动 now
项目了!
Next.js
也可使用其余的托管方案,更多详细能够看一下这部份内容 'Deployment' 注意:咱们推荐你推送 .next
,或者你自定义的打包输出目录(到托管方案上)(Please have a look at 'Custom Config',你还能够自定义一个专门用于放置配置文件(例如 .npmignore
或 .gitignore
)的文件夹。不然的话,使用 files
或者 now.files
来选择要部署的白名单(很明显要排除掉 .next
或你自定义的打包输出目录)
你能够将你的 Next.js
应用当成一个不依赖于 Node.js服务的
静态应用。此静态应用支持几乎全部的 Next.js
特性,包括 异步导航、预获取、预加载和异步导入等。
首先,Next.js
的开发工做没什么变化,而后建立一个 Next.js
的配置文件 config,就像下面这样:
// next.config.js
module.exports = {
exportPathMap: function() {
return {
'/': { page: '/' },
'/about': { page: '/about' },
'/readme.md': { page: '/readme' },
'/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } },
'/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } },
'/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } }
}
}
}
复制代码
须要注意的是,若是声明的路径表示的是一个文件夹的话,那么最终将会导出一份相似于
/dir-name/index.html
的文件,若是声明的路径是一个文件的话,那么最终将会以指定的文件名导出,例如上述代码中,就会导出一个readme.md
的文件。若是你使用了一个不是以.html
结尾的文件,那么在解析此文件的时候,你须要给text/html
设置一个Content-Type
头(header
)
经过上述的相似代码,你能够指定你想要导出的静态页面。
接着,输入如下命令:
next build
next export
复制代码
或许,你还能够在 package.json
文件中多添加一条命令:
{
"scripts": {
"build": "next build && next export"
}
}
复制代码
如今就只须要输入这一条命令就好了:
npm run build
复制代码
这样,你在 out
目录下就有了一个当前应用的静态网站了。
你也能够自定义输出目录,更多帮助能够在命令行中输入
next export -h
获取
如今,你就能够把输出目录(例如/out
)部署到静态文件服务器了,须要注意的是,若是你想要部署到 Github
上的话,那么须要须要增长一个步骤
例如,只须要进入 out
目录,而后输入如下命令,就能够把你的应用部署到 ZEIT now
now
复制代码
当你输入 next export
命令时,咱们帮你构建了应用的 HTML
静态版本,在此阶段,咱们将会执行页面中的 getInitialProps
函数。
因此,你只能使用 context
对象传递给 getInitialProps
的 pathname
、query
和 asPath
字段,而 req
或 res
则是不可用的(res
和 res
只在服务器端可用)。
基于此,你也没法在咱们预先构建
HTML
文件的时候,动态的呈现HTML
页面,若是你真的想要这么作(指动态构建页面)的话,请使用next start
不管是开发者体验仍是终端表现,它都超出预期,因此咱们决定将它共享到社区中。
客户端包的大小根据每一个应用程序的功能等不一样而不尽相同。 一个最简单的 Next
程序包在 gzip
压缩后可能只有 65kb 大小。
是也不是。 说是,是由于两者都让你的开发变得更轻松。 说不是,则是由于 Next.js
强制规定了一些目录结构,以便咱们能实现更多高级的操做,例如:
SSR
)此外,Next.js
还内置了两个对于单页应用来讲比较重要的特性:
<Link>
(by importing next/link
)<head>
元素的方法(经过导入 next/head
)若是你想在 Next.js
或其余 React
应用中复用组件,则使用 create-react-app
是一个很好的选择,你能够稍后将其导入以保证代码库的纯净。
Next.js
自带的库 styled-jsx支持 局部(scoped
)css
,固然,你也能够在 Next
应用中添加上面所提到的任何你喜欢的代码库来使用你想要的 CSS-in-JS
解决方案。
Next.js
自带的库 styled-jsx支持 局部(scoped
)css
,固然,你也能够在 Next
应用中使用如下示例中的任何一种 CSS
预处理器方案:
(语法特性)咱们参照 V8
引擎,由于 V8
普遍支持 ES6
和 async
以及 await
,因此咱们也就支持这些,由于 V8
还不支持类装饰器(class decorator
),因此咱们也就不支持它(类装饰器)
Next.js is special in that:
getInitialProps
that should block the loading of the route (either when server-rendering or lazy-loading)基于上述几个特色,咱们可以构造出一个具备如下两个功能的简单路由:
url
对象来检查 url
或者 修改历史记录<Link />
组件做为相似于 <a/>
等标签元素的容器以便进行客户端的页面切换。咱们已经在一些颇有意思的场景下测试了路由的灵活性,更多相信能够看这里 nextgram
Next.js
提供了一个 request handler
,利用其咱们可以让任意 URL
与 任何组件之间产生映射关系。 在客户端,<Link />
组件有个 as
属性,它可以改变获取到的 URL
这由你决定, getInitialProps
是一个 异步(async
)函数(或者也能够说是一个返回 Promise
的标准函数),它可以从任意位置获取数据。
固然,这还有个用 Apollo 的例子呢。
固然,这也有个例子。
这是一个已知的 Next.js
架构问题,在解决方案还没内置到框架中以前,你能够先看看这一个例子中的解决方法来集中管理你的路由。
咱们在发布初版的时候就已经提供了不少例子,你能够看看这个目录
咱们力求达到的目标大部分都是从 由 Guillermo Rauch
给出的[设计富Web应用的 7个原则]中受到启发,PHP
的易用性也是一个很棒的灵感来源,咱们以为在不少你想使用 PHP
来输出 HTML
页面的状况下,Next.js
都是一个很好的替代品,不过不像 PHP
,咱们从 ES6
的模块化系统中得到好处,每一个文件都能很轻松地导入一个可以用于延迟求值或测试的组件或函数。
当咱们研究 React
的服务器端渲染时,咱们并无作出太大的改变,由于咱们偶然发现了 React
做者 Jordan Walke
写的 react-page (now deprecated)。
Please see our contributing.md