原文地址css
Naoyuki Kanezawa (@nkzawa), Guillermo Rauch (@rauchg) 和 Tony Kovanen (@tonykovanen)
周二,2016年10月25日前端
咱们很是自豪的开源了Next.js,他是一个小巧的基于React、Webpack、Babel的客户端渲染universal JavasScript web app框架。node
要开始使用Next.js,只需在一个有package.json
的文件夹里执行如下命令:react
$ npm install next --save $ mkdir pages
生成pages/index.js
:jquery
import React from 'react' export default () => <div>Hello world!</div>
而后在package.json
里添加一个script:git
{ "scripts": { "dev": "next" } }
而后执行:github
$ npm run dev
本文主要阐述该项目的设计理念和它背后的哲学思想。web
若是想学习如何使用Next.js,请移步README,在那你只需花几分钟就能学习完全部功能。npm
首先咱们会深刻该项目的后端并逐一描述如下6个原则:json
零配置,使用文件系统做为API
只有JavaScript,一切都是函数
自动服务端渲染和代码分割
彻底可定制的数据获取方式
预测是提升性能的关键
部署简单
从不少年之前,咱们就一直追求universal JavaScript applications的道路。
Node.js展现了这种可能性,客户端服务端代码共享,也扩展了开发者的视野。
为了能在Node上开发app和网页,开发者作了不少不少尝试。无数的模板语言和框架应运而生……可是技术始终被分割为前端和后端。
假设你选择Express和Jade开发,HTML会首先被服务端渲染,而后另一个项目(jQuery或是其余相似库)才会接管过去。
这样的情况其实一点也不比传统的PHP方式好多少。从不少方面来讲,PHP都更适合服务端渲染HTML这样的工做。在出现async
/await
以前,在JS中查询数据并不容易。捕获和处理request/response的异常也很是麻烦。
然而,一些值得关注的概念的出现,使得咱们可以填补这个空白。其中最重要的就是可以根据数据返回对应UI的纯函数。
这个模型(因React而流行)很是重要,不过仅仅有他还不能从众多的模板系统中脱颖而出。另一个重要的概念就是组件生命周期。
生命周期的钩子函数容许咱们在前端接管某些以前由服务端渲染的的页面。好比说,你能够在一开始只渲染静态数据,监听服务端的更新,并根据数据改变页面。或者什么也不作,让这个页面保持静态。
Next.js是咱们在这条路上更近一步的成果。
工具假设你的项目具备特定的文件结构。
通常来讲,咱们开始一个新项目时,都会新建一个文件夹,在里面放一个package.json
,在./node_modules
中安装模块。
Next.js扩展了这种结构,引入了一个放置顶级组件的文件夹叫pages
。
例如,你能够新建pages/index.js
,它会自动映射到/
路由:
import React from 'react' export default () => <marquee>Hello world</marquee>
而后新建pages/about.js
,它会映射到/about
路由:
import React from 'react' export default () => <h1>About us</h1>
咱们相信这是一个很好的起步默认配置,并且很是便于项目浏览。当须要更复杂的路由时,咱们也容许开发人员自行控制[#25]。
启动一个项目所须要的全部操做仅仅是运行:
$ next
除非必要,没有额外的配置。自动代码热替换,自动错误报告,自动source maps,自动为老旧浏览器编译代码。
每一个Next.js的路由都是一个仅仅是一个export
一个函数或一个继承自React.Component
的子类所构成的ES6模块。
这个方式和其余相似模型相比的好处是,整个系统都能保持高可组合性和可测试性。一个组件能够被直接渲染也能够被其余顶级组件导入并渲染。
组件也能够改变整个page的<head>
:
import React from 'react' import Head from 'next/head' export default () => ( <div> <Head> <meta name="viewport" content="width=device-width, initial-scale=1" /> </Head> <h1>Hi. I'm mobile-ready!</h1> </div> )
而且,不须要任何包装或改动就能对整个系统进行测试。只需在你的测试集中导入并shallow-render
你的路由。
拥抱CSS-in-JS
。经过使用glamor使得咱们能在彻底不理会CSS解析和编译的状况下拥有完整的CSS功能:
import React from 'react' import css from 'next/css' export default () => <p className={style}>Hi there!</p> const style = css({ color: 'red', ':hover': { color: 'blue' }, '@media (max-width: 500px)': { color: 'rebeccapurple' } })
咱们认为这种方式提供了无与伦比的性能,可组合性以及和服务器端渲染流程的良好集成。咱们在FAQ中会讨论更多关于这个决定的一切。
有两个很是想实现同时又很是困难的任务:
服务端渲染
代码分割
在Next.js中,每一个pages/
下面的组件都会自动的连同内联的脚本一块儿被服务端渲染。
当组件是经过<Link />
或路由自动加载时,咱们会获取一个基于JSON的页面,这个页面一样会包含他本身的脚本。
这意味着一个页面能够有不少的imports:
import React from 'react' import d3 from 'd3' import jQuery from 'jquery'
… 这并不会对其他的页面有任何影响。
这点对于那些须要技术业务需求不一样的团队互相合做的场景下特别有用。一个组件的性能问题不会影响到整个系统。
服务端渲染的静态JSX确实很是了不得,但现实世界的应用每每须要处理来自不一样API调用的数据。
Next.js给React的组件添加了一个重要的扩展:getInitialProps
。
import React from 'react' import 'isomorphic-fetch' export default class extends React.Component { static async getInitialProps () { const res = await fetch('https://api.company.com/user/123') const data = await res.json() return { username: data.profile.username } } }
咱们关于转换哪些功能的立场简单来讲就是:咱们牢牢跟随V8。由于咱们的目标是服务端和客户端的代码共享,当咱们用Chrome或者Brave开发,并在Node上执行代码时,这种作法给了咱们极好的体验。
正如你所见,咱们的扩展很是简单:getInitialProps
必须返回一个能resolve
为一个JavaScript对象的Promise
,该对象会被用来生成组件的props
。
这使得Next.js能很好的和REST APIs、GraphQL,甚至是全局状态管理Redux等很好的协做,这有一个示例在咱们的wiki上。
不管组件是服务端渲染的仍是经过客户端路由动态加载的,均可以使用同一个方法得到数据:
static async getInitialProps ({ res }) { return res ? { userAgent: res.headers['user-agent'] } : { userAgent: navigator.userAgent } }
咱们认为即便没有网络也能给予用户即时响应的能力使得彻底服务端渲染偏向“单页应用”或“彻底没有服务端渲染”两个极端。
在www.zeit.co咱们在Next.js上实现了一种技术,让咱们能同时享受两种方式各自的好处:每一个<Link />
标签都会在后台经过一个ServiceWorker提早获取组件的JSON表现。
一旦预加载完成,若是你在页面上随意跳转,你点击的某个连接或路由已经提早加载好了。
更好的是,由于数据也经过一个专用的方法getInitialProps
,咱们能提早加载而不用怕引起没必要要的服务端负载和数据加载。这比以前的web 1.0预加载机制强多了。
咱们建立Next.js是由于咱们相信同构的应用是将来web应用的重要组成部分。
提早绑定和编译(预测)是一个很是有效的部署方式。
部署一个Next.js应用只须要运行next build
和next start
。
你的package.json
文件和如下相似:
{ "name": "my-app", "dependencies": { "next": "*" }, "scripts": { "dev": "next", "build": "next build", "start": "next start" } }
这样,你就简单的部署成功了。
最后,这是咱们对于这个特定问题的贡献。咱们认为他在灵活性和好用的默认配置之间取得了不错的平衡,不过这确定不是解决全部问题的方法。
在接下来的几周里,咱们但愿能更多的讨论和思考其余解决方案,好比Vue.JS, Gatsby, Ember+Fastboot等等。若是你有兴趣加入咱们的社区,作出本身的贡献,请必定要加入zeit.chat, 查看issues,参与将来方向的讨论。