原文地址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,參與將來方向的討論。