Counts the Clouds

Next.jsでブログを構築する (4)

Next.jsでブログを構築する〜細部の調整
2021.11.18

Next.js

florian-klauer--K6JMRMj4x4-unsplash.jpg

記事のパーマリンクをあわせる

もともとのブログと記事のパーマリンクを同一にしたい。

pages/[category]/[slug].js[category]/[slug].jsにしたかったけど、pagesのルートに[year][category]を同時に置こうとしたら怒られた。

% yarn dev
yarn run v1.22.11
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info  - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
event - compiled successfully
Error: You cannot use different slug names for the same dynamic path ('year' !== 'category').
...
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

妥協して、記事のパーマリンクのほうを優先して、年別・月別アーカイブの方はパスを切ってリダイレクトをかけることにした。

/posts/[category]/[slug].js -> /[category]/[slug].js
/[year].js -> /date/[year].js
/[year]/[month].js -> /date/[year]/[month].js
/[year]/pages/[page].js -> /date/[year]/pages/[page].js
// next.config.js
// ...
module.exports = {
  // ...
  async redirects() {
    return [
      {
        source: "/:year(\\d{4})/:path*",
        destination: "/date/:year/:path*",
        permanent: true,
      },
    ];
  },
};

これはかんたんに変更できた。

スラッグの修正

同様にパーマリンクを合わせたいので、slug({日付}-{スラッグ})から日付部分を取りたい。Middleman時代のslugが日付なし形式だったため。

日付以降の名前に重複はないはずだったが、日付を除去したときの重複をチェックすると、1件重複が確認された。

チェックはテストに組み込んだ。

// api.test.js
import { getPostFileNames } from "./api";

describe("api", () => {
  test("no files duplication", () => {
    const files = getPostFileNames();
    const slugs = files
      .map((file) => file.replace(/^\d{4}\-\d{2}\-\d{2}\-/, ""))
      .filter((e, i, a) => a.indexOf(e) === i);
    expect(slugs.length).toBe(files.length);
  });
});

ファイル名から.htmlを除去したときに誤って名前に必要なhtmlも削除してしまっていた。たぶん置換条件に.を入れていなかったのだろう。

(脱線)Unixコマンドでファイルリネーム

renameコマンドはmacには搭載されていない。brewでインストールできるけど、とりあえずなのでコマンドのほうがよさそう。

find <検索対象ディレクトリパス> -type f | sed 'p;s/<置換条件>/<置換文字列>/' | xargs -n2 mv

正しいファイル名に変換しなおした。日付を除いてもファイル名に重複はない。

find posts -type f | sed 'p;s/\.html\.md'/.md/ | xargs -n2 mv

Vercelへのデプロイ

Vercelへのデプロイはgithubのリポジトリを教えるだけで終わり。本当に簡単すぎる。

ビルドに10分ぐらいかかる。タイムアウトなしで処理してくれるのでありがたいが、時間はなんとかしたい。たぶんアーカイブでremark-prism変換しているせい。

記事の抜粋を生成するのにmarkdownToHtml関数を援用していたので、それぞれ独立させ、抜粋を生成するときはremark-prismで変換しないようにした。つまり記事の抜粋にはsyntax highlightingは使えないが、データ上はなさそうなのでよしとした。

import { remark } from "remark";
import html from "remark-html";

export default async function markdownToDigest(markdown) {
  const digest = markdown.split(/<!--\s?more\s?-->/)[0];
  const result = await remark().use(html, { sanitize: false }).process(digest);
  return await result.toString();
}

これでビルドにかかる時間は2〜3分となった。

ダイナミックページ

Next.jsはSSGとSSRを両立できるので、ためしにダイナミックページを追加してみる。

データソースとしては、簡単のため気象庁のjsonを使うことにした。

getServerSidePropsで取ってきたデータを返してやればよし。

スタイルは簡単にstyled jsxを使った。こちらはサーバーではレンダリングされず、フロントエンドで追加される。

// pages/tenki.js
import React from "react";

function Tenki({ data }) {
  return (
    <div className="Tenki">
      <style jsx>{`
        .Tenki {
          padding: 1rem;
        }
        th,
        td {
          padding: 0.5rem;
        }
      `}</style>
      <table>
        <tbody>
          {Object.entries(data).map(([key, val]) => (
            <tr key={key}>
              <th>{key}</th>
              <td>{val.replace(/ /g, "")}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export async function getServerSideProps(context) {
  const result = await fetch(
    "https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json",
  );
  const data = await result.json();
  return {
    props: {
      data,
    },
  };
}

export default Tenki;

デプロイしてみるが、SSGとSSRで見た目でわかる差はない。

リンクを張ってみればわかるかもと思ったが、その場合はSPAとして動くのでサーバーの動作は確認できない。なんてこった。

いろいろ調べると、開発者ツールでJavaScriptを切ればいいらしい。

Chromeの開発者ツールを開いて、Preference→Debugger→Disable JavaScriptをチェックしてJavaScriptを切る。

すると、トップページは2桁ms、ダイナミックページは3桁msで、体感できる違いを確認できた。

まとめにならないまとめ

非常に乱文乱筆、雑多な内容になってしまったが、公開することに意義があると思って記事にした。

Next.js+Vercelでブログを構築するのはフロントエンド開発に慣れている人なら簡単だと思う。

SSG、SSRを柔軟に選べるし、今回は使っていないが/apiディレクトリでBFFの面倒まで見てくれるので至れり尽くせり。

Gatsbyとの比較でいうと、自由度が高いので新しい方もNext.jsで作ればよかったと思うぐらいにはいい開発体験だった。