Skip to content
Permalink
Browse files
Update
  • Loading branch information
mizchi committed May 18, 2020
1 parent d01650c commit fd552c7d717fba7773b7de844f3fa607102e44e6
Showing 5 changed files with 294 additions and 5 deletions.
@@ -0,0 +1,110 @@
---
title: Incremental Static Regeneration で実現する次世代のサーバーアーキテクチャ
created: 1589802285779
tags: [next, react, ssg]
---

next.js 9.4 に Incremental Static Regeneration という実験的な新機能があります。

[Blog \- Next\.js 9\.4 \| Next\.js](https://nextjs.org/blog/next-9-4)

パッと見、「段階的な静的サイト生成…?なんのことだろう…」となったのですが、手元で試してみた感じ、これが既存のサーバーの実装アプローチを変える、革命的な機能ではないかと思いました。

解説を書きつつ、どのような応用があるか解説します。

## next.js の Incremental SSG を試してみる

リポジトリはここです。 [mizchi/issg\-playground](https://github.com/mizchi/issg-playground)

解説にあたっては、必要なのはほぼこのファイルだけで、短いのでそのまま貼ります。

```tsx
// pages/[slug].tsx
import { GetStaticProps, GetStaticPaths } from "next";
type Props = {
slug: string;
builtAt: number;
};
export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
return {
props: {
slug: ctx.params.slug as string,
builtAt: Date.now(),
},
unstable_revalidate: 30,
};
};
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: ["/foo"],
fallback: true,
};
};
export default (props: Props) => {
return (
<>
{props.slug}: {props.builtAt}
</>
);
};
```

- `pages/[slug].tsx``/*` をハンドルします。
- `export const getStaticProps` はそのルーティングに来たときのルート要素に渡す props を組み立てます
- `export const getStaticPaths` はそのルーティングに来たときの、パス一覧を返却します。

これらの機能は元々 `next export` の静的アセットの吐き出しのために、手元のビルド時に一回だけ実行されるものでした。(このブログも、この機能を使って生成されています)

しかし、 Incremental SSG では、このサーバーは静的に吐き出してデプロイされるのではなく、 server or serverless モードでデプロイすることを想定されています。
静的アセットの吐き出しサーバーを、動的なサーバーとしてデプロイする、とはどういうことでしょうか。

ここで、 `unstable_revalidate: 30``fallback: true` に注目してください。

- `getStaticPaths``fallback: true` が指定されると、 `paths` で指定されなかったパスも、 `getStaticProps` のロジックに応じて組み立てられます。
- `getStaticProps``unstable_revalidate: 30` のような値を返すと、 30 秒間は静的アセットとして返却されます

ここからが面白くて

- unstable_revalidate: 30` の秒数が経過後、次のリクエストが発生した際に、一旦はキャッシュを返しつつ、バックグラウンドでもう一度そのページを構築
この挙動が面白いですね。
つまりは、静的サイトジェネレータとしてある程度の運用の容易さを担保しつつ、CDN のスケーラビリティを借りて、かつ、ある程度は動的な振る舞いを取れる、ということです。
完全なアプリケーション・サーバーとしては、
## フロントエンドベストプラクティスの実現
自分は [光を超えるためのフロントエンドアーキテクチャ \- Speaker Deck](https://speakerdeck.com/mizchi/guang-wochao-erutamefalsehurontoendoakitekutiya) という発表をしたことがあります。要約すると、パフォーマンス最適化のためには、リクエストを全部アプリケーション・サーバーに到達させてはだめで、 CDN Edge に置いた HTML に当てつつ、キャッシュごとにサロゲートキーを当てて、リソースの更新のたびにプログラマブルなインバリデーションを発行する、というものです。
当時、これを実現できるのは fastly しかありませんでした。
まだプログラマブルなインバリデーションはないのですが、RFC のディスカッションを読む限りは、rauchg と Timer 曰く、もっと多機能なものも考えているらしいので、 それを想定してるように見えます。
[RFC: Incremental Static Regeneration · Discussion \#11552 · zeit/next\.js](https://github.com/zeit/next.js/discussions/11552)
## Vercel / SmartCDN / Next.js のゴールが見えた
正直なところ、 next.js が静的 export に対応した当時は、プロダクトとしての軸がぶれているように感じました。SSR のフレームワークでいきたいのか、 SSG になりたいのか、どっちなのかと。そして自前の PaaS を運用しているのは、よくわからないところがありました。
Vercel (旧名 now.sh) は SmartCDN という機能があります。これはおそらく、この機能を見据えたプログラマブルな CDN として設計されたものだったのでしょう。
Incremental SSG は、NoCode などの Headless CMS のガワとして、next.js を使うことが想定されています。これらの NoCode Backend はお世辞にもスケーラビリティがあるとは言えないものが多く、またレスポンスタイムに難があることが多かったのですが、Incremental SSG モードの Next.js をかぶせることで(初回アクセスをやや犠牲にしつつも) CDN のスケーラビリティの恩恵を受けることができます。
おそらく Vercel + SmartCDN は、next に最適化された CMS バックエンドとして、オールインワンパッケージを提供するのがゴールなのでしょう。
## 未来
とりあえず RFC に fastly の SurrogateKeys 相当のキャッシュタグみたいなものがほしい!とだけ書いておきました。
[RFC: Incremental Static Regeneration · Discussion \#11552 · zeit/next\.js](https://github.com/zeit/next.js/discussions/11552#discussioncomment-14415)
また、ZEIT, あらため Vercel は 20 億円の増資を受けたことで、next.js エコシステムの発展は、より加速していくように思います。
[\(20\) Shu Uesugi さんは Twitter を使っています 「🆕Next\.js の開発元でもある ZEIT はこのたび社名変更し Vercel になりました。 🎉2100 万ドルの資金調達も発表。 👨🏻‍💻 私はご縁があり 2 月に開発者としてジョインしました。 🤔「○○ は今後どうなるの?」というご質問につきましては、こちらの Notion ドキュメントをご一読ください!→ https://t\.co/eCwc23gIzo」 / Twitter](https://twitter.com/chibicode/status/1252745903540105216)
ちょっと前まで、next.js は意見が強いフレームワークで、正直 nuxt のほうが使いやすいよなぁ、と思ってたんですが、こういう感じで攻めてくるのは以外で、びっくりしつつも、応援したい気持ちがあります。
@@ -24,7 +24,7 @@
"@types/react-dom": "^16.9.7",
"@types/styled-components": "^5.1.0",
"amdx-loader": "^0.8.0",
"amdxg-cli": "^0.8.2",
"amdxg-cli": "^0.8.4",
"raw-loader": "^4.0.1",
"rimraf": "^3.0.2",
"rollup": "^2.9.1",
@@ -0,0 +1,23 @@
import Head from "next/head";
import { Layout } from "amdxg-components";
// @ts-ignore
import { frontmatter } from "../slides/develop-mizchi-dev.mdx";
// @ts-ignore
import rawMdx from "!raw-loader!../slides/develop-mizchi-dev.mdx";

import config from "../amdxg.config";
import pages from "../gen/pages.json";
import { parse } from "amdx";

export const config = { amp: true };

export default () => {
return (
<>
<Head>
<title>{frontmatter.title}</title>
</Head>
<Layout config={config}></Layout>
</>
);
};
@@ -0,0 +1,156 @@
---
title: next.js で自分のブログを作る
---

新しく自分のブログとして [mizchi.dev](https://mizchi.dev) を作った話

---

## Lighthouse

![](https://i.gyazo.com/718543e55f8ca7c35e81bb67aa5cfa79.png)

---

## Full AMP

![](https://i.gyazo.com/bcc2d128064304c355d3c776d41a22a8.png)

---

## GA 対応

![](https://gyazo.com/10b9063d30ff5fb73ec2cc5a3b824333.png)

---

## Git から編集ヒストリの生成

![](ehttps://i.gyazo.com/13034861f0afceacf47ad8d8a09f239a.png)

---

## どんなブログがほしかったか

- Lighthouse で満点出したい
- => どうせ動かないし CDN 上で静的サイト + Full AMP
- 普通の Markdown じゃつまんないから MDX で書きたい
- => コンパイラごと作った(amdx)
- サーバーの運用をしたくない
- => netlify + 買ったまま忘れてたカスタムドメイン(mizchi.dev)
- next.js の最適化に乗りたい
- => `pages/*.tsx` が公開される仕組みを、そのまま採用

---

## next.js の SSG + AMP モードの採用

```tsx
// pages/foo.tsx
export const config = { amp: true };
export default function Foo() {
return <div>foo</div>;
}
```

- `amp: true` で常に amp を生成
- AMP canonical は常に自分自身を指す(のを next.js が勝手にやってくれる)

---

## AMP の plugin を諸々突っ込む

- `amp-social-share` で twitter / facebook / hatena bookmark のシェアに対応
- `amp-analytics` で GoogleAnalytics 対応
- rollup + preact + amp-script で、AMP 上で動的なコンポーネントが作れる。後で何かに使う

---

# AMP 用 Markdown Compiler を作ろう!

---

## AMP 環境の Markdown に求められる仕様

- AMP の仕様を満たす
- `img` => `amp-img` かつ、 amp layout 仕様を満たす固定幅の要素に
- 数式ブロック(`$$ ~ $$`) を amp-mathml に変換
- コードハイライト: **ランタイムで構文解析できない** ので、コードブロックのハイライトを、コンパイル時に済ませておく必要

---

## MDX について

Markdown 中で import 構文が使える仕様

```
# Hello, MDX
import Doc from "./doc";
<Doc />
```

Markdown ドキュメントから、別の Markdown ドキュメントや、React.Component
を展開できる。
シンタックスハイライターなどのエコシステムの都合から、`.mdx`拡張子をそのまま採用したい。

---

## AMDX: Accelarated MDX

- [mizchi/amdx: Accelarated MDX](https://github.com/mizchi/amdx)
- remark ベースで `@mdx-js/mdx` を元に拡張 (中で使ってる babel plugin はそのままなので、構文は互換)
- refract(prismjs parser) で、コンパイル時にコードブロックをトークン化
- +色々 (toc, frontmatter や WebWorker で動くように等)

---

## AMDXG: AMDX による静的サイト生成ツールキット

- amdx-loader: amdx の webpack 用のローダー
- amdxg-components: ブログ用のデフォルトコンポーネント集。使わなくてもいい
- amdxg-cli: ページの雛形や各種メタデータ生成用の CLI
- amdxg-boilerplate: ただのボイラープレート。(注意: まだ安定してない。頻繁に変わる)

---

## 使い方

```
$ npx degit mizchi/mdxx/templates/blog my-blog
$ cd my-blog
$ git init && git commit -m "Init" # 編集履歴生成に git history を使う
$ edit amdxg.config.js # メタデータを編集
# 書く
$ npm i -g amdxg-cli
$ amdxg new:page new-article # docs/new-article.mdx に記事の雛形を生成
$ npx run dev # localhost:3000 でプレビューしながら記事を編集
# build / deploy
$ npx run build # out/ に静的サイトを生成
# 要: netlify account
$ npm i -g netlify-cli
$ netlify deploy -d out --prod
```

---

## 残 TODO

- CSS が雑なのでちゃんとやる
- amxd のプレビュー環境を作る (mdbuf.netlify.com ベースで)
- amdx|amdxg のドキュメントサイトを作る
- vercel 上で任意のバックエンド(CMS)から Incremental SSG する例を作る

(余談だが、 9.4 新機能の Incremental SSG がとても良いですね…)

---

## おわり

Google 検索結果からの遷移が爆速 💪

CSS が苦手なので助けて
@@ -1556,10 +1556,10 @@ amdx@^0.8.0:
unist-util-visit "^2.0.2"
yaml "^1.8.3"

amdxg-cli@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/amdxg-cli/-/amdxg-cli-0.8.2.tgz#02da2749db9dc69d246c9bf4918386f174de9eb5"
integrity sha512-wGRBsd6V5FEWp3ZobfvjojpS3bhLIg3Q1961mUHuS0rBKVBvJB8MFAaJ5GgXk8QgDHVBH5wBE6k4oKxHk/EJ2w==
amdxg-cli@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/amdxg-cli/-/amdxg-cli-0.8.4.tgz#49e7b6a967417fae790709323177a63d0c98a71b"
integrity sha512-uzw1s10F1O/EpyN664v7gBz1vkp8xqQ8q+CgQW3ZjuX87WSmncEWpS+O/Mx7xuB9al1LFnts1huwuPuGifb77A==
dependencies:
fs-extra "^9.0.0"
lodash.sortby "^4.7.0"

0 comments on commit fd552c7

Please sign in to comment.