https://zeit.co/blog/next の翻訳です。


> Naoyuki Kanezawa (@nkzawa), Guillermo Rauch (@rauchg) and Tony Kovanen (@tonykovanen)
> 2016年10月26日

私たちは、サーバレンダリングされるユニバーサルなJavaScriptウェブアプリのための小さなフレームワークであり、ReactやWebpack、Babel上に構築され、このサイト(訳注: https://zeit.co )を動作させているNext.jsをオープンソース化することをとても嬉しく思います!

The "Hello World" of Next.js
(Next.jsの"Hello World")

Next.jsを使い始めるには、package.jsonを含む新しいディレクトリ内で次のように実行してください。

$ npm install next --save
$ mkdir pages

pages/index.jsを追加してください。

import React from 'react'
export default () => <div>Hello world!</div>

package.jsonにこのようにスクリプトを追加してください。

{
  "scripts": {
    "dev": "next"
  }
}

そして起動します。

$ npm run dev

本記事では、プロジェクトの理念や設計決定について述べたいと思います。

代わりにNext.jsの使い方を学ぶには、ほんの数分で機能全体を学ぶことができるREADMEを参照してください。最初にプロジェクトの背景に触れ、それから6つの基本原則について述べたいと思います。

  1. ゼロ設定。ファイルシステムをAPIとして使用する
  2. JavaScriptのみ。すべてが関数
  3. 自動サーバレンダリングとコード分割
  4. データ取得は開発者次第
  5. 予測がパフォーマンスの鍵
  6. シンプルなデプロイ

背景

ここ何年もの間、私たちはユニバーサルなJavaScriptアプリケーションの構想を追求してきました。

Node.jsはクライアントとサーバ間でコードを共有できるようにし、世界中の多くの開発者たちに貢献の間口を広げることで、その道筋を先導してきました。

Nodeでのアプリやウェブサイト開発を実用化するために多くの試みがなされました。多くのテンプレート言語やフレームワークが作れられましたが、フロントエンドとバックエンドの技術的な分裂は残ったままでした。

ExpressとJadeを例に上げるならば、いくらかのHTMLはサーバでレンダリングされ、(jQueryや同様のライブラリで動作する)別のコードベース が引き継ぎます。

その状況は、例えばPHPに比べてより良いものではありませんでした。多くの点で、PHPは実際のところ”HTMLのサーバレンダリング”によりふさわしいものでした。async/awaitの登場前には、JSでデータサービスにクエリを投げるのは困難でした。リクエスト・レスポンスの範囲(scope)でエラーを補足し制限するのも難しいことが知られていました。

しかし、その後の注目すべき概念の変化がこの溝を埋めてくれたのです。最も重要なものは、その時点の利用可能なデータをもとにUI表現を返す純粋なレンダリング関数の登場です。

(Reactで人気となった)このモデルはとても重要なものですが、これ自体では殆どのテンプレートシステムの動作と必ずしも違いはありません。その他の重大な概念はコンポーネントのライフサイクルです。

ライフサイクルフックにより、サーバで生じたレンダリングを継続して処理することができるようになります。例えば、静的なデータ表現から始まり、サーバからのリアルタイム更新を受け取り、時間とともに変更することができます。もしくは静的なままであるかもしれません。

Next.jsはこの構想をどのように推し進めるかについての私たちの見解です。

ゼロ設定。ファイルシステムをAPIとして使用する

Next.jsは、あなたのファイルシステム内のプロジェクト構造についていくつか仮定を行います。

例えば一般的に、新しいディレクトリを作り、package.jsonを中に配置し、モジュールを./node_modulesへインストールすることでNode.jsプロジェクトを開始します。

Next.jsはトップレベルコンポーネントが置かれるpagesサブディレクトリによりこの構成を拡張します。

例えば、/にマッピングされるpages/index.jsをこのように追加することができます。

import React from 'react'
export default () => <marquee>Hello world</marquee>

そして、/aboutにマッピングされるpages/about.jsはこのようになります。

import React from 'react'
export default () => <h1>About us</h1>

これは優れたデフォルト設定であり、プロジェクトを非常に迅速に進めることを可能にします。より高度なルーティングが必要な場合は、開発者がリクエストをインターセプトし、制御できるようにします[#25]。

プロジェクトに取り掛かるのに必要なのは次のように実行することだけです。

$ next

必要がなければ設定はありません。最新コードの再読込(hot-code reloading)、エラー表示、ソースマップ、古いブラウザ用のトランスパイルが自動で行われます。

JavaScriptのみ。すべてが関数

Next.js内のすべてのルートは、React.Componentを継承したクラスや関数をエクスポートした単なるES6モジュールです。

同様の方法に比べこのアプローチの利点は、システム全体が非常に組み立て可能(composable)でテスト可能(testable)なままであることです。例えば、コンポーネントを直接レンダリングしたり、読み込んで他のトップレベルのコンポーネントによってレンダリングすることができます。

コンポネントはページの<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>
)

さらに、このシステムをテスト可能にするためのラッパや変換は必要ありません。あなたのテストスイートは、単にルート(コンポーネント)を読み込んでレンダリングすることができます。

また、私たちはCSS-in-JSを導入することを決断しました。CSSの構文解析とコンパイルの必要なしに、完全で制約のないCSSを使うことができるとても優れたglamorライブラリを使用します。

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'
  }
})

このモデルは、サーバーレンダリングのパイプラインにおいて優れたパフォーマンス、組み立て可能性(composability)と統合を与えてくれると考えます。FAQでより詳細にこの決定について取り扱っています。

自動サーバレンダリングとコード分割

とても困難でありながら非常に望まれてきた2つの課題があります。

  • サーバーレンダリング
  • アプリのビルドをより小さな結合ファイル(bundle)へ分割すること

Next.jsを使用すると、pages/以下のすべてのコンポーネントは自動でサーバーレンダリングされ、そのスクリプトはインライン化されます。

コンポーネントが、<Link />またはルータを介して動的に読み込まれると、(依存)スクリプトを含むJSONベースのページデータを取得します。

これは、あるページが広範なインポート一覧を含んでいるかもしれないということを意味します。

import React from 'react'
import d3 from 'd3'
import jQuery from 'jquery'

... それらは他のページのパフォーマンスには影響しません。

これは、非常に異なる技術およびビジネス要件を持つコンポーネントを使って共同作業する大規模なチームには特に強力です。チームまたは個人によって発生するパフォーマンスの低下は組織のその他には影響しません。

データ取得は開発者次第

静的なJSXのサーバーレンダリングは重要ではあるものの、実際のアプリケーションでは、異なるAPI呼び出しやネットワークリクエストから得られる動的データを扱います。

Next.jsは、Reactコンポーネントにとても重要な拡張を行います。getInitialPropsです。

データを取ってくる(fetch)ページは次のようになります。

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 }
  }
}

(async/awaitのように)どの機能をトランスパイルするかについての私達の方針は、V8の性能をターゲットにする、というようにまとめることができます。私たちの目標は、サーバーとクライアント間のコード共有なので、たとえば、Node上でコードを実行し、BraveやChromeで開発する場合、この方針は素晴らしいパフォーマンスを与えてくれます。

見ていただいたとおり、この取り決めは非常にシンプルかつ柔軟です。getInitialPropsは、JavaScriptオブジェクトに解決されるPromise、もしくはJavaScriptオブジェクトを返す必要があり、それらはコンポーネントのpropsに追加されます。

Next.jsは、Wikiの例にあるように、REST API、GraphQL、さらにはグローバルな状態管理ライブラリであるReduxを上手く受け入れることができます。

同じ方法で、コンポネントがサーバレンダリングされるかクライアントで動的レンダリングされるかによって、異なるデータを読み込むことが可能になります。

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 preload strategies)と比べて実質的な利益となります。

シンプルなデプロイ

私たちは、ユニバーサルで同形(isomorphic)なアプリケーションがウェブの将来の大部分をしめると考えているため、Next.jsを作りました。

事前結合とコンパイル(予測)が効果的で効率的なデプロイには重要です。

Next.jsアプリをデプロイするのに必要なのは、next buildに続いてnext startを実行することだけです。

△nowを使用する場合、package.jsonは次のようになります。

{
  "name": "my-app",
  "dependencies": {
    "next": "*"
  },
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}

それからデプロイするには単に now を実行します。

最後に、この非常に興味深い問題へのコントリビューションについてです。Next.jsは柔軟性とスマートなデフォルト値との間の良いバランスを保っていると思いますが、必ずしもすべての問題への解決方法ではありません。

今後数週間で、Vue.JSGarsbyEmber+Fastbootおよび他の多くのように、この目標を達成するためにその他のすばらしいアプローチを議論し取り上げることを楽しみにしています。

もしあなたがコントリビューションに興味があるなら、zeit.chatに参加し、問題点を調べ、🚲 将来の方向性について議論・検討してください。