まずは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を追加。
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
これで、ファイルシステムがそのままAPIになるらしい。
入ったバージョンはこんな感じ。
"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
export default () => <div>Welcome to next.js!</div>
npm run dev
http://localhost:3000/
にアクセスして・・・
SSRできてるかわからないけど、表示はできた。
ちなみにこれで
・Webpack + Babelによるトランスパイルとバンドル
・ホットリロード
・SSR(と、indexing・・って何?)
・静的ファイルの提供
ができているらしい(すごい!
コード分割
ページ単位でコードが分割され、不要なファイルは読み込まないようになっているはず・・。
ルーティングと合わせてみてみましょ。
export default () => <div>split</div>
http://localhost:3000/split
にアクセスして・・・
# 多分ここなんじゃないかな・・
$ 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プロパティを設定しておくと、設定が重複しても後勝ちになるようです。
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すれば良い模様。
import React, {Component} from 'react';
export default class extends Component {
render() {
return (
<div>
<p>fecth</p>
</div>
);
};
};
次にページロード時にデータを取得して、それをpropsに反映させるところですが、
非同期で静的な関数「getInitialProps」を用いるようです。
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」を用いる。
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>
)
hrefに指定するリンクはObjectでも良いみたい。
Node.jsのURLオブジェクトで定義されているプロパティは全て使えるようです。
replaceプロパティ
HTMLのHistoryAPIはご存知でしょうか。
僕は知りませんでした(Σ
こちらがとてもわかりやすい。
簡単にご説明しますと、
replaceプロパティをつけることで、ブラウザの履歴に遷移元が追加されません。
試してみましょう。
・・・
<Link href="/fetch" replace>
・・・
遷移 | 履歴 | |
---|---|---|
1 | 初回ページ(ここではgoogle.comにします) | google.com |
2 | localhost/index | |
3 | localhost/fetch | localhost/fetch |
↑こんなイメージです。fetchページで戻る操作をしても、
indexページが履歴にないので、google.comに戻るはずです。
子コンポーネントの話
Linkの下に置く子コンポーネントは、基本的にaタグが望ましいようです。
それは次のような理由からのようです。
####[1]onClickプロパティを持っていないと、Linkとしての動作をしてくれない
これはつまり、遷移すらしないことになります。
試してみましょう。
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>
);
}
}
onClickを持たせてみましょう。
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持ってないやつなんておらへんやろwww
って思うかもしれませんが、カスタムコンポーネントの場合は注意が必要ですね。
[2]aタグでない場合、hrefプロパティは渡されない
[1]の結果から、子がonClickを持っていれば、遷移はするようです。
(遷移先でのgetInitialPropsも動きます)
ですが、hrefは渡されていないのであれば、SEO的に良くないですよね。
<div id="__next">
<div>
<div>Welcome to next.js!</div>
<div>onClick・・ありますぜ・・?</div>
</div>
</div>
補足:aタグならなんでも良いわけではない
<Link>のhrefに値を指定した場合、
描画されるHTMLには、子のaにhrefが設定されます。
<Link href="/hoge">
<a>hoge</a>
</Link>
<a href="/hoge">hoge</a>
しかし、aのラッパーを作成すると、ハイパーリンクになりません。
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を使います。
・・・
<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かです。
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を流し読みして
(疲れました)
自分なりに翻訳、解釈しただけみたいになってしまいましたが、
基本的なところはわかりやすくまとめられたのではないでしょうか!
基礎から脱線しているような項目は、一旦読み飛ばしたので、
別の記事にして改めて学びたいと思います。