xiaofeihe

react服务端渲染框架next.js

Next.js文档地址:https://nextjs.org/

创建Next.js项目脚手架:https://github.com/zeit/create-next-app

官方Demo:https://github.com/zeit/next.js/tree/canary/examples

官方Demo已经很全了,有不懂的地方认真看下。


以下为官方文档翻译:

安装

安装next.js:

1
npm install --save next react react-dom

同时添加script到package.json文件:

1
2
3
4
5
6
7
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}

自动代码拆分

CSS

Built-in CSS support

我们捆绑了styled-jsx以支持隔离的作用域CSS。目的是支持类似于Web Components的shadow CSS,遗憾的是它不支持服务器渲染并且只支持JS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function HelloWorld() {
return (
<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>
);
}

export default HelloWorld;

有关更多示例,请参阅styled-jsx文档。

CSS-in-JS

可以使用任何现有的CSS-in-JS解决方案。最简单的是内联样式:

1
2
3
4
5
function HiThere() {
return <p style={{ color: 'red' }}>hi there</p>;
}

export default HiThere;

要使用更复杂的CSS-in-JS解决方案,通常必须为服务器端呈现实现样式刷新。我们通过允许您定义包装每个页面的自定义组件来实现此功能。

Importing CSS / Sass / Less / Stylus files

要支持导入.css.scss.less.styl文件,您可以使用这些模块,这些模块为服务器呈现的应用程序配置合理的默认值。

@zeit/next-css
@zeit/next-sass
@zeit/next-less
@zeit/next-stylus

Static file serving (e.g.: images)

在项目根目录中创建一个名为static的文件夹。然后,您可以从代码中使用/ static / URLs引用这些文件:

1
2
3
4
5
function MyImage() {
return <img src="/static/my-image.png" alt="my image" />;
}

export default MyImage;

注意:不要将静态目录命名为其他任何名称。该名称是必需的,是Next.js用于提供静态资产的唯一目录。

Populating <head>

我们公开了一个内置组件,用于将元素附加到页面的<head>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Head from 'next/head';

function IndexPage() {
return (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
);
}

export default IndexPage;

为了避免<head>中的重复标记,您可以使用key属性,这将确保标记仅呈现一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Head from 'next/head';

function IndexPage() {
return (
<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>
);
}

export default IndexPage;

Fetching data and component lifecycle

当需要状态、生命周期挂钩或初始数据填充时,可以导出React.Component或使用无状态函数和钩子。

使用无状态函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react';

class HelloUA 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>;
}
}

export default HelloUA;

路由

Next.js不提供应用程序中所有可能的路由清单,因此当前页面不知道客户端上的任何其他页面。为了扩展性,所有后续路由都会被延迟加载。

可以通过一个<Link>组件来启用路由之间的客户端转换。

对于需要硬刷新的静态页面,如使用AMP时,不需要此组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// pages/index.js
import Link from 'next/link';

function Home() {
return (
<>
<ul>
<li>Home</li>
<li>
<Link href="/about">
<a>About Us</a>
</Link>
</li>
</ul>

<h1>This is our homepage.</h1>
</>
);
}

export default Home;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// pages/about.js
function About() {
return (
<>
<ul>
<li>
<Link href="/">
<a>Home</a>
</Link>
</li>
<li>About Us</li>
</ul>

<h1>About</h1>
<p>We are a cool company.</p>
</>
);
}

export default About;

Dynamic Routes

1
2
3
4
5
6
7
8
9
10
import { useRouter } from 'next/router';

const Post = () => {
const router = useRouter();
const { postId } = router.query;

return <p>My Blog Post: {postId}</p>;
};

export default Post;

一个链接/post/first-post像下面这样:

1
2
3
<Link href="/post/[postId]" as="/post/first-post">
<a>First Post</a>
</Link>

With URL object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// pages/index.js
import Link from 'next/link';

function Home() {
return (
<div>
Click{' '}
<Link href={{ pathname: '/about', query: { name: 'Zeit' } }}>
<a>here</a>
</Link>{' '}
to read more
</div>
);
}

export default Home;

Replace instead of push url

<Link>组件的默认行为是将一个新的urlpush堆栈中。您可以使用replace属性来防止添加新条目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// pages/index.js
import Link from 'next/link';

function Home() {
return (
<div>
Click{' '}
<Link href="/about" replace>
<a>here</a>
</Link>{' '}
to read more
</div>
);
}

export default Home;

Using a component that supports onClick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pages/index.js
import Link from 'next/link';

function Home() {
return (
<div>
Click{' '}
<Link href="/about">
<img src="/static/image.png" alt="image" />
</Link>
</div>
);
}

export default Home;

Disabling the scroll changes to top on page

<Link>的默认行为是滚动到页面的顶部。当定义了散列时,它将滚动到特定的id,就像普通的<a>标记一样。为了防止滚动到top / hash scroll={false},可以将其添加到<Link>

1
2
<Link scroll={false} href="/?counter=10"><a>Disables scrolling</a></Link>
<Link href="/?counter=10"><a>Changes with scrolling to top</a></Link>

Imperatively

您还可以使用next/router

1
2
3
4
5
6
7
8
9
10
11
import Router from 'next/router';

function ReadMore() {
return (
<div>
Click <span onClick={() => Router.push('/about')}>here</span> to read more
</div>
);
}

export default ReadMore;

Intercepting popstate

With URL object

Router Events

Shallow Routing

useRouter

Using a Higher Order Component

Prefetching Pages

With

1
2
3
<Link href="/about" prefetch={false}>
<a>About</a>
</Link>

Imperatively

大多数预取需求都由<link/>解决,但我们也公开了一个用于高级使用的命令式API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useRouter } from 'next/router';

export default function MyLink() {
const router = useRouter();

return (
<>
<a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
A route transition will happen after 100ms
</a>
{// and we can prefetch it!
router.prefetch('/dynamic')}
</>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useRouter } from 'next/router'

export default function MyLink() {
const router = useRouter()

useEffect(() => {
router.prefetch('/dynamic')
})

return (
<a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
A route transition will happen after 100ms
</a>
)
}

export default withRouter(MyLink)

在使用React.Component时,还可以将其添加到ComponentDidMount生命周期方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 (
<a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
A route transition will happen after 100ms
</a>
);
}
}

export default withRouter(MyLink);

API Routes

AMP Support

`

Enabling AMP Support

若要启用对页的AMP支持,请向页面中添加ExportConstconfig={amp:true}

AMP First Page

1
2
3
4
5
6
// pages/about.js
export const config = { amp: true };

export default function AboutPage(props) {
return <h3>My AMP About Page!</h3>;
}

Hybrid AMP Page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// pages/hybrid-about.js
import { useAmp } from 'next/amp';

export const config = { amp: 'hybrid' };

export default function AboutPage(props) {
return (
<div>
<h3>My AMP Page</h3>
{useAmp() ? (
<amp-img
width="300"
height="300"
src="/my-img.jpg"
alt="a cool image"
layout="responsive"
/>
) : (
<img width="300" height="300" src="/my-img.jpg" alt="a cool image" />
)}
</div>
);
}

AMP Page Modes

AMP Behavior with next export

Adding AMP Components

AMP社区提供了许多组件,使AMP页面更具交互性。您可以使用Next/Head将这些组件添加到页面中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// pages/hello.js
import Head from 'next/head';

export const config = { amp: true };

export default function MyAmpPage() {
return (
<div>
<Head>
<script
async
key="amp-timeago"
custom-element="amp-timeago"
src="https://cdn.ampproject.org/v0/amp-timeago-0.1.js"
/>
</Head>

<p>Some time: {date.toJSON()}</p>
<amp-timeago width="0" height="15" datetime={date.toJSON()} layout="responsive">
.
</amp-timeago>
</div>
);
}

AMP Validation

TypeScript Support

Amp目前还没有内置的TypeScript类型,但是它在他们的路线图中。作为解决办法,您可以手动将这些类型添加到amp.d.ts中。

Static HTML export

Limitation

Multi Zones

How to define a zone

没有与专区相关的API。您只需执行以下操作:

1.确保在你的应用程序中只保留你需要的页面,这意味着一个应用程序不能有来自另一个应用程序的页面,如果应用程序A有/blog,那么应用程序B就不应该拥有它。
2.确保添加资产前缀以避免与静态文件的冲突。

How to merge them

可以使用任何HTTP代理合并区域。
您现在可以使用dev作为本地开发服务器。它允许您轻松地为多个应用程序定义路由,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
{
"version": 2,
"builds": [
{ "src": "docs/next.config.js", "use": "@now/next" },
{ "src": "home/next.config.js", "use": "@now/next" }
],
"routes": [
{ "src": "/docs/_next(.*)", "dest": "docs/_next$1" },
{ "src": "/docs(.*)", "dest": "docs/docs$1" },
{ "src": "(.*)", "dest": "home$1" }
]
}

对于生产部署,可以使用相同的配置并立即运行,以便立即使用Zeit进行部署。否则,还可以将代理服务器配置为使用一组路由(如上面的路由)进行路由。

Recipes

1.设置301重定向
2.只处理SSR和服务器模块
3.基于React-Material-UI-Next-Express-Mongoose-Mongodb的建筑
4.使用React-Material-UI-Next-MobX-Express-Mongoose-MongoDB-TypeScript构建SaaS产品

Contributing

请看我们的contributing.md

Authors

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