| 1/24

next.js で自分のブログを作る

自分のブログとして mizchi.dev を作った話

at 隅田川.js #1(オンライン) - connpass


next.js で自分のブログを作る

自分のブログとして mizchi.dev を作った話

at 隅田川.js #1(オンライン) - connpass


何を作ったか

このブログ自身(mizchi.dev)。スライドツールも突貫で自作

ソースコード mizchi/dev


Lighthouse


Full AMP


GA 対応


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


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

  • Lighthouse で満点出したい
  • 普通の Markdown じゃつまんないから MDX で書きたい
  • サーバーの運用をしたくない
  • next.js の最適化に乗りたい

作った

  • どうせ動かないし CDN 上で静的サイト + Full AMP
  • MDX コンパイラを自作 (amdx)
  • netlify + 買ったまま忘れてたカスタムドメイン(mizchi.dev)
  • pages/*.tsx が公開される仕組みを、そのまま採用

(本音

  • next.js で静的サイト運用の知見を貯めたい
  • AMP の知見を貯めたい
  • React でガシガシ動く作例を置く場所を、自分のドメインで用意したい

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

// 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
  • 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: ただのボイラープレート。(注意: まだ安定してない。頻繁に変わる)

ドキュメントの解析

docs/*.mdx の frontmatter を解析

---
title: hello
created: 1589877769475
tags: [react, next, amp]
---
  • タイトルを日付順に並べた gen/pages.json を生成
  • タグで逆引きする用の gen/tagmap.json を生成
  • git log のコミットログから gen/*.history.json を生成

Index

  • gen/pages.json からページ一覧を表示
  • gen/tagmap.json からタグ一覧を表示

ドキュメントの表示

  • next.config.jswebpack.mdx 拡張子は amdx-loader (自作) を通すようにする
  • next.js の danymic routing で 対応する slug の component を動的に返す
// pages/[slug].tsx
export const getStaticProps: GetStaticProps = async (props) => {
  const slug = props.params.slug;
  const { default: Doc, toc, frontmatter } = await import(
    `../docs/${slug}.mdx`
  );
  const { default: history } = await import(`../gen/${slug}.history.json`);
  return {
    props: {
      slug,
      toc,
      history,
      tags: frontmatter.tags || [],
      frontmatter: frontmatter || { title: slug, created: 0, tags: [] },
      html: ReactDOMServer.renderToStaticMarkup(<Doc amp />),
    } as Props,
  };
};

後は amdxg-components で用意したコンポーネントに突っ込んで表示


タグページ

  • gen/tagmap.json から /tags/react のようなページを生成して表示
// pages/tags/[tag].tsx
export function getStaticPaths() {
  const paths = Object.keys(tagmap).map((tag) => {
    return `/tags/${tag}`;
  });
  return {
    paths,
    fallback: false,
  };
}
export const getStaticProps: GetStaticProps = async (props) => {
  const tag = props.params.tag;
  return {
    props: {
      tagName: tag,
      pages: tagmap[tag as any],
    } as Props,
  };
};

自分の環境で遊ぶ

$ npx degit mizchi/mdxx/templates/blog myblog
$ cd myblog
$ 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

デモ


今後やること

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

Incremental Static Regeneration で実現する次世代のサーバーアーキテクチャ - mizdev


おわり

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

(CSS が苦手なので助けて)