Help us understand the problem. What is going on with this article?

Next.jsを知りたいのだ

More than 1 year has passed since last update.

まずはQiitaの記事で軽めの知識を入れて見る

参考:Next.js

  • SSRされるユニバーサルJSアプリのためのフレームワーク

  • ディレクトリとファイルの構成を、そのままルーティングとして定義できる
     ∟さらに自動でSSRにも対応する(すごい!
     ∟コード分割も自動で

  • nextコマンドは、ホットリロードやトランスパイルなどを諸々やってくれる(すごい!

  • getInitialPropsなる拡張があり、そこにデータ取得とpropsへの追加ができる
     ∟サーバとクライアントで異なるデータを読み込むこともできる

  • タグがServiceWorkerを経由してデータ取得をするため、パフォーマンスの向上が見込める?(よくわかっていない

  • デプロイも楽

How to useで勉強してみよう

公式

※それだけで別の記事にできそうなプリフェッチ関連や、
 設定済みなもののカスタマイズなどは別の機会に学びます。

SetUp

mkdir next_sample && cd next_sample
npm init
npm install --save next react react-dom

package.jsonにscriptsを追加。

package.json
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },

これで、ファイルシステムがそのままAPIになるらしい。

入ったバージョンはこんな感じ。

package.json
  "dependencies": {
    "next": "^6.0.3",
    "react": "^16.4.0",
    "react-dom": "^16.4.0"
  }

Welcome to Next.js!

pagesディレクトリは、APIとマッピングするはず。
なので・・・

mkdir pages && touch pages/index.js
pages/index.js
export default () => <div>Welcome to next.js!</div>
nextの実行
npm run dev

http://localhost:3000/
にアクセスして・・・

welcome.png

SSRできてるかわからないけど、表示はできた。

ちなみにこれで

・Webpack + Babelによるトランスパイルとバンドル
・ホットリロード
・SSR(と、indexing・・って何?)
・静的ファイルの提供

ができているらしい(すごい!

コード分割

ページ単位でコードが分割され、不要なファイルは読み込まないようになっているはず・・。

ルーティングと合わせてみてみましょ。

pages/split.js
export default () => <div>split</div>

http://localhost:3000/split
にアクセスして・・・

split.png
bundleの確認
# 多分ここなんじゃないかな・・
$ cd .next/dist/bundles
$ tree
.
└── pages
    ├── _app.js
    ├── _app.js.map
    ├── _document.js
    ├── _document.js.map
    ├── _error.js
    ├── _error.js.map
    ├── index.js
    ├── index.js.map
    ├── split.js
    └── split.js.map

CSS-in-JSについて

サーバ側のレンダリング用に、style flushingを実装する必要があり(なんだそれ)
カスタムコンポーネントを定義する必要があるらしい。

一旦ここでは保留で。

静的ファイル

画像などの静的ファイル
staticディレクトリを作成して、そこに入れて呼び出す。

/static/my-image.png

headタグにコンポーネントを追加し隊(<Head>)

<div>Welcome to next.js!</div>

こんなJSXですが、
描画されるHTMLにはしっかりhtml、head、bodyタグなどが含まれています。

<!DOCTYPE html>
<html>
  <head>
    <meta charSet="utf-8" class="next-head"/>
    <link rel="preload" href="/_next/-/page/index.js" as="script"/>
    <link rel="preload" href="/_next/-/page/_app.js" as="script"/>
    <link rel="preload" href="/_next/-/page/_error.js" as="script"/>
    <link rel="preload" href="/_next/static/commons/manifest.js" as="script"/>
    <link rel="preload" href="/_next/static/commons/main.js" as="script"/></head>
  <body>
    <div id="__next">
      <div>Welcome to next.js!</div>
    </div>
    <div id="__next-error"></div>
    <script>
            __NEXT_DATA__ = {"props":{"pageProps":{}},"page":"/","pathname":"/","query":{},"buildId":"-","assetPrefix":"","nextExport":false,"err":null,"chunks":[]}
            module={}
            __NEXT_LOADED_PAGES__ = []
            __NEXT_LOADED_CHUNKS__ = []

            __NEXT_REGISTER_PAGE = function (route, fn) {
              __NEXT_LOADED_PAGES__.push({ route: route, fn: fn })
            }

            __NEXT_REGISTER_CHUNK = function (chunkName, fn) {
              __NEXT_LOADED_CHUNKS__.push({ chunkName: chunkName, fn: fn })
            }

            false
    </script>
    <script async="" id="__NEXT_PAGE__/" src="/_next/-/page/index.js"></script>
    <script async="" id="__NEXT_PAGE__/_app" src="/_next/-/page/_app.js"></script>
    <script async="" id="__NEXT_PAGE__/_error" src="/_next/-/page/_error.js"></script>
    <script src="/_next/static/commons/manifest.js"></script>
    <script src="/_next/static/commons/main.js"></script>
  </body>
</html>

bodyは普通に書けるとして、headは・・?

ご安心ください。
Next.jsは用意しておりました。

「next/head」を使うようです。

追加するタグにはkeyプロパティを設定しておくと、設定が重複しても後勝ちになるようです。

pages/index.js
import Head from 'next/head';

export default () => (
    <div>
        <Head>
            <title key="title">たいとる</title>
        </Head>
        <div>Welcome to next.js!</div>
    </div>
)
たいとるが追加されてるねっ
<head>
  <meta charSet="utf-8" class="next-head next-head"/>
  <title class="next-head">たいとる</title>
  <link rel="preload" href="/_next/-/page/index.js" as="script"/>
  <link rel="preload" href="/_next/-/page/_app.js" as="script"/>
  <link rel="preload" href="/_next/-/page/_error.js" as="script"/>
  <link rel="preload" href="/_next/static/commons/manifest.js" as="script"/>
  <link rel="preload" href="/_next/static/commons/main.js" as="script"/>
</head>

データ取得とライフサイクルの話

まず、stateやライフサイクルフック、初期データの投入をしたくなったら、
普通にReact.Componentをextendsすれば良い模様。

pages/fetch.js
import React, {Component} from 'react';

export default class extends Component {
    render() {
        return (
            <div>
                <p>fecth</p>
            </div>
        );
    };
};

次にページロード時にデータを取得して、それをpropsに反映させるところですが、
非同期で静的な関数「getInitialProps」を用いるようです。

pages/fetch.js
import React, {Component} from 'react';

export default class extends Component {
    static async getInitialProps() {
        /* この辺でデータを取得したり */
        return {
            hoge: 'hoge'
        };
    }

    render() {
        return (
            <div>
                <p>fecth</p>
                <div>{this.props.hoge}</div>
            </div>
        );
    };
};

getInitialPropsは、
初期ロード時はサーバでのみ実行される。
・以降、クライアントでgetInitialPropsを実行させたい場合、
 <Link />コンポーネントによる移動か、ルーティングAPIを用いる。
・子コンポーネントで定義できない

getInitialPropsは以下のプロパティを持つオブジェクトを引数で受け取るようです。

プロパティ 説明
pathname URLのパス /fetch
query URLのクエリ文字列(Object化されてる) {}
asPath 実際のパスの文字列(クエリを含む) /fetch
req リクエストオブジェクト(サーバのみ) IncomingMessage {...}
res レスポンスオブジェクト(サーバーのみ) ServerResponse {...}
jsonPageRes レスポンスオブジェクトを取得する(クライアントのみ)
err レンダリング中にエラーが発生した場合のエラーオブジェクト undefined

ルート間の遷移(<Link />)

getInitialPropsの説明で出てきた<Link />コンポーネント、
「next/link」を用いる。

pages/index.js
import Head from 'next/head';
import Link from 'next/link';

export default () => (
    <div>
        <Head>
            <title key="title">たいとる</title>
        </Head>
        <div>Welcome to next.js!</div>
        <Link href="/fetch">
            <a>fetchページへ</a>
        </Link>
    </div>
)

pagemove.gif

hrefに指定するリンクはObjectでも良いみたい。
Node.jsのURLオブジェクトで定義されているプロパティは全て使えるようです。

replaceプロパティ

HTMLのHistoryAPIはご存知でしょうか。
僕は知りませんでした(Σ

こちらがとてもわかりやすい。

簡単にご説明しますと、
replaceプロパティをつけることで、ブラウザの履歴に遷移元が追加されません。

試してみましょう。

pages/index.js
・・・
        <Link href="/fetch" replace>
・・・
遷移 履歴
1 初回ページ(ここではgoogle.comにします) google.com
2 localhost/index
3 localhost/fetch localhost/fetch

↑こんなイメージです。fetchページで戻る操作をしても、
indexページが履歴にないので、google.comに戻るはずです。

replace.gif

子コンポーネントの話

Linkの下に置く子コンポーネントは、基本的にaタグが望ましいようです。

それは次のような理由からのようです。

[1]onClickプロパティを持っていないと、Linkとしての動作をしてくれない

これはつまり、遷移すらしないことになります。

試してみましょう。

pages/index.js
import Head from 'next/head';
import Link from 'next/link';
import React, {Component} from 'react';

export default () => (
    <div>
        <Head>
            <title key="title">たいとる</title>
        </Head>
        <div>Welcome to next.js!</div>
        <Link href="/fetch">
            <NoOnClick />
        </Link>
    </div>
);

class NoOnClick extends Component {
    render() {
        return (
            <div>onClickなんて持ってませんよ</div>
        );
    }
}

noOnClick.gif

onClickを持たせてみましょう。

pages/index.js
import Head from 'next/head';
import Link from 'next/link';
import React, {Component} from 'react';

export default () => (
    <div>
        <Head>
            <title key="title">たいとる</title>
        </Head>
        <div>Welcome to next.js!</div>
        <Link href="/fetch">
            <OnClick />
        </Link>
    </div>
);

class OnClick extends Component {
    render() {
        return (
            <div
              onClick={this.props.onClick}
            >
                onClick・・ありますぜ・・?
            </div>
        );
    }
}

OnClick.gif

きょうび、onClick持ってないやつなんておらへんやろwww
って思うかもしれませんが、カスタムコンポーネントの場合は注意が必要ですね。

[2]aタグでない場合、hrefプロパティは渡されない

[1]の結果から、子がonClickを持っていれば、遷移はするようです。
(遷移先でのgetInitialPropsも動きます)

ですが、hrefは渡されていないのであれば、SEO的に良くないですよね。

onClickがあるけどhrefが渡されない場合のHTML結果
<div id="__next">
    <div>
        <div>Welcome to next.js!</div>
        <div>onClick・・ありますぜ・・?</div>
    </div>
</div>

補足:aタグならなんでも良いわけではない

<Link>のhrefに値を指定した場合、
描画されるHTMLには、子のaにhrefが設定されます。

JSXがこうなら
<Link href="/hoge">
  <a>hoge</a>
</Link>
HTMLはこうなる
<a href="/hoge">hoge</a>

しかし、aのラッパーを作成すると、ハイパーリンクになりません。

pages/index.js
import Head from 'next/head';
import Link from 'next/link';
import React, {Component} from 'react';

export default () => (
    <div>
        <Head>
            <title key="title">たいとる</title>
        </Head>
        <div>Welcome to next.js!</div>
        <Link href="/fetch" >
                <MyAnchr>fetchページへ</MyAnchr>
        </Link>
    </div>
);

class MyAnchr extends Component {
    render() {
        return (
            <a style={{color: 'red'}} href={this.props.href}>
                {this.props.children}
            </a>
        );
    }
}

出力されるHTMLをみてみると、aにhrefがついていません。

<a style="color:red">fetchページへ</a>

そんな時はpassHrefを使います。

pages/index.js
・・・
        <Link href="/fetch" passHref>
                <MyAnchr>fetchページへ</MyAnchr>
        </Link>
・・・

この辺をみる限り、
styled-componentsを使う際に、スタイリングしたaタグにhrefが渡らない問題があったようですね。

passHrefの挙動は、私も最初全然わからなかったので、
実際にpassHrefが追加された時のコミットをみると、
少しわかった気になれますので、気が向いたら確認すると良さそうです。

ルーティングAPI

next/linkとは別のアプローチで、ルート間の遷移が可能なようです。

next/routerはルーティングAPIのようです。

ルート間遷移(push、replace)

push、replaceメソッドを用いれば、指定したルートに遷移できます。

違いはpushStateかrelaceStateかです。

pages/index.js
import Head from 'next/head';
import Router from 'next/router'
import React, {Component} from 'react';

export default () => (
    <div>
        <Head>
            <title key="title">たいとる</title>
        </Head>
        <div>Welcome to next.js!</div>
        <input 
            type="button" 
            value="fetchへ" 
            onClick={() => Router.push('/fetch')}
        />
    </div>
);

ページ内リンクなら、同じpropsを用いて、getInitialPropsを呼び出さない
シャローな呼び出し方法もできるようです。

onClick={() => Router.push('/fetch', '/fetch', { shallow: true})}

他のAPI

そんなものがあるんだーって感じです。

API 説明
route 現在のルート
pathname クエリ文字列を除く現在のパス
query URLのクエリ文字列(Object化されてる)
asPath 実際のパスの文字列(クエリを含む)がブラウザに表示される
beforePopState(cb = function) popStateのインターセプト

ルータのイベント

ルータのイベントを監視することもできるようです。

今のところ、そんなものがあるんだーって感じです。

監視方法 説明
onRouteChangeStart(url) ルートの変更が開始されたとき
onRouteChangeComplete(url) ルートが完全に変更されたとき
onRouteChangeError(err、url) ルートの変更時にエラーが発生した場合
onBeforeHistoryChange(url) ブラウザの履歴を変更する直前
onHashChangeStart(url) ハッシュ変更ありページ変更なしの開始時・・?
onHashChangeComplete(url) ハッシュ変更ありページ変更なしの完了時・・?

dynamic import

next/dynamicを用いて、
動的import(ReactCompomentも含む)ができるようです。

import dynamic from 'next/dynamic';

・・・

dynamic(import(./hoge));

ちょこっと動かしてみたのですが、
有用性がよくわからなくて、コードを乗せるレベルに達しませんでした・・。

Hot to useを流し読みして

(疲れました)

自分なりに翻訳、解釈しただけみたいになってしまいましたが、
基本的なところはわかりやすくまとめられたのではないでしょうか!

基礎から脱線しているような項目は、一旦読み飛ばしたので、
別の記事にして改めて学びたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away