完成したチャートを Next.js + Cloudflare Pages で公開|連載シリーズ最終回

ClaudeCode
NextJS
CloudflarePages
デプロイ
SEO

ついにこの日が来ました。Part 1 で claude --version を叩き、Part 2 でスキル化を覚え、Part 3-17 で 15 種類のチャートを Claude Code に作らせ、Part 18 で R2 キャッシュを噛ませ、Part 19 でテストを書いた——その総決算が今回の Part 20、「世に出す」 です。

ローカルで npm run dev して綺麗に描画されるチャートを見て満足するのは、エンジニアの自己満足です。チャートは URL を持って、検索結果に出て、SNS でシェアされて、初めて社会的な価値 を持ちます。本記事では、これまで連載で作ってきたチャート群を Next.js App Router + Cloudflare Pages に乗せ、OG 画像を自動生成し、Google Search Console と Analytics で計測まで仕込んで「公開完了」と言える状態に持っていきます。

所要時間 2 時間。Claude Code に頼めば、Next.js のページコンポーネント・OG 画像ジェネレーター・robots.ts / sitemap.ts の SEO 制御・Cloudflare Pages のビルド設定までを 1 セッションで 書き上げてくれます。連載 20 本の長旅、最後まで一緒に走り抜けましょう。

なぜ Cloudflare Pages なのか

Next.js のデプロイ先候補は Vercel・Netlify・AWS Amplify・Cloudflare Pages など複数ありますが、stats47.jp は Cloudflare Pages を選んでいます。理由は 3 つ。

観点VercelCloudflare Pages
月額コスト(中小規模)Hobby 無料 / Pro $20無料枠が広い(Workers 100K req/day)
エッジ実行Edge Functions 限定Workers ですべて Edge
帯域課金あり(100GB/月超)無制限・無料
D1 / R2 連携別契約同一ダッシュボード
ビルド時間速いやや遅い(5-10 分)

決定打は 「帯域が無料」「D1 / R2 がそのまま使える」 の 2 点。Part 18 で R2 にキャッシュした e-Stat JSON を、同じ Cloudflare アカウント内で読みに行けるので、認証も IAM もいりません。env.STATS_DATA_BUCKETwrangler.toml に書くだけ。

ビルドがやや遅いのは唯一の弱点ですが、CI 完走で 5-7 分なら許容範囲。連載のゴールは「個人の趣味で 47 都道府県データを公開する」レベルなので、Cloudflare 一択で問題ありません。

Next.js App Router のページ構成

連載では Pages Router ではなく App Router を使ってきました。理由は (a) Server Components で R2 fetch を サーバー側に閉じ込められる、(b) opengraph-image.tsx で OG 画像が宣言的に作れる、(c) metadata export で SEO 設定が型安全、の 3 点です。

ディレクトリ構造はこんな感じ。

apps/web/src/app/
├── layout.tsx                       # ルート layout(Header/Footer)
├── page.tsx                         # トップページ
├── robots.ts                        # robots.txt 自動生成
├── sitemap.ts                       # sitemap.xml 自動生成
├── opengraph-image.tsx              # サイト全体の OG 画像
└── charts/
    ├── page.tsx                     # チャート一覧
    └── [slug]/
        ├── page.tsx                 # 個別チャートページ
        ├── opengraph-image.tsx      # チャート別 OG 画像
        └── loading.tsx              # Suspense fallback

[slug] 部分が動的ルーティング。/charts/population-bar/charts/aging-heatmap のように、Part 3-17 で作った 15 種類のチャートがそれぞれ URL を持ちます。

Claude Code に頼むときはこの構造を最初に伝えておくと、迷子になりません。

あなた → claude:
  apps/web/src/app/charts/[slug]/page.tsx を作って。
  R2 の app/charts/[slug]/data.json を fetch して、
  packages/visualization の BarChart に流す。
  generateStaticParams で 15 個の slug を静的生成。
  generateMetadata で title/description を frontmatter から拾う。

Step 1: 静的生成 vs ISR vs CSR の選択

ページを書く前に、レンダリング戦略 を決めます。Next.js App Router では (a) Static(ビルド時生成)、(b) ISR(Incremental Static Regeneration)、(c) Dynamic(リクエスト時 SSR)、(d) Client-only の 4 つから選べます。

戦略適する用途TTFB更新頻度Cloudflare Pages 対応
Static (SSG)統計データ・ブログ記事最速ビルド時のみフル対応
ISR頻繁に更新される一覧速いrevalidate 秒数で再生成一部制約あり
Dynamic (SSR)ユーザー固有データリクエストごとWorkers 経由
Client (CSR)インタラクティブ UI遅い(JS 待ち)リアルタイム制約なし

連載のチャートは e-Stat の年次データ なので、データ更新は年 1-2 回程度。SSG 一択 です。generateStaticParams で 15 個の slug を返し、ビルド時に静的 HTML + JSON を生成します。

// apps/web/src/app/charts/[slug]/page.tsx
import { notFound } from "next/navigation";
import { fetchChartData } from "@/lib/r2";
import { ChartRenderer } from "@/components/ChartRenderer";

const CHART_SLUGS = [
  "population-bar",
  "aging-heatmap",
  "medical-cost-choropleth",
  "income-scatter",
  "birthrate-line",
  "bar-chart-race",
  "radar-prefecture",
  "wage-box-plot",
  "tourism-stacked",
  "energy-area-chart",
  "crime-small-multiple",
  "commerce-bubble",
  "edu-slope-graph",
  // ...
] as const;

export async function generateStaticParams() {
  return CHART_SLUGS.map((slug) => ({ slug }));
}

export const dynamicParams = false; // CHART_SLUGS 以外は 404

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const data = await fetchChartData(slug);
  if (!data) return {};
  return {
    title: `${data.title}|stats47`,
    description: data.description,
    openGraph: {
      images: [`/charts/${slug}/opengraph-image`],
    },
  };
}

export default async function ChartPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const data = await fetchChartData(slug);
  if (!data) notFound();
  return (
    <article className="mx-auto max-w-3xl px-4 py-8">
      <h1 className="text-2xl font-bold">{data.title}</h1>
      <p className="mt-2 text-slate-600">{data.description}</p>
      <ChartRenderer type={data.chartType} data={data.values} />
    </article>
  );
}

ポイントは 3 つ。

  1. generateStaticParams で 15 個の slug を返し、dynamicParams = false で範囲外を 404 に
  2. params が Promise(Next.js 15 以降の仕様変更)。await params を忘れると runtime error
  3. generateMetadata で OG 画像 URL を /charts/[slug]/opengraph-image に向ける(次の Step で自動生成される)

Claude Code に「Next.js 15 の App Router でチャートページを書いて」と頼むと、Next.js 14 流の params: { slug: string } を返してくることがあります。「Next.js 15 系を使うので params は Promise」 と明示するのが事故防止のコツ。

Step 2: チャート JSON を R2 から fetch(Part 18 と連携)

fetchChartData の中身は Part 18 で作った R2 reader を呼び出すだけです。

// apps/web/src/lib/r2.ts
import { getCloudflareContext } from "@opennextjs/cloudflare";

export type ChartData = {
  title: string;
  description: string;
  chartType: "bar" | "line" | "scatter" | "choropleth" | "heatmap";
  values: unknown;
};

export async function fetchChartData(
  slug: string,
): Promise<ChartData | null> {
  const { env } = getCloudflareContext();
  const key = `app/charts/${slug}/data.json`;
  const obj = await env.STATS_DATA_BUCKET.get(key);
  if (!obj) return null;
  const json = await obj.json<ChartData>();
  return json;
}

R2 のキーパス規約(.claude/rules/r2-storage-design.md)に従って app/charts/[slug]/data.json に統一しています。all.json モノリスを作らず、URL 1 個 = JSON 1 個 の原則で並べておくと、(a) Cloudflare のエッジキャッシュが効きやすい、(b) 1 ページの fetch が小さい(典型 5-50 KB)、(c) snapshot 更新時の差分が読みやすい——というメリットがあります。

ビルド時に R2 を読みに行く処理は Cloudflare Pages のビルドコンテナから直接 可能。@opennextjs/cloudflaregetCloudflareContext が、build / dev / prod すべての環境で同じインターフェースを提供してくれます。

Step 3: OG 画像を Next.js の opengraph-image.tsx で自動生成

SNS でシェアされたときのカード画像、いわゆる OG 画像。Twitter / Facebook / Slack で URL を貼ったときに出るあれです。

これを チャート 1 個ずつ手動で作る のは現実的じゃない。15 種類 × 47 都道府県 = 705 枚の OG 画像を Photoshop で作るなんて発狂します。

Next.js App Router には opengraph-image.tsx という規約があり、ファイルを置くだけで OG 画像エンドポイントが生える 仕組みがあります。中身は ImageResponse(Vercel/Edge の Satori ベース)で JSX を SVG → PNG に変換します。

// apps/web/src/app/charts/[slug]/opengraph-image.tsx
import { ImageResponse } from "next/og";
import { fetchChartData } from "@/lib/r2";

export const runtime = "edge";
export const alt = "stats47 チャート";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default async function OgImage({
  params,
}: {
  params: { slug: string };
}) {
  const data = await fetchChartData(params.slug);
  const title = data?.title ?? "stats47";
  const subtitle = data?.description ?? "47 都道府県の統計データ";

  return new ImageResponse(
    (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          justifyContent: "space-between",
          width: "100%",
          height: "100%",
          padding: "64px",
          background:
            "linear-gradient(135deg, #0f172a 0%, #1e3a8a 100%)",
          color: "#fff",
          fontFamily: "sans-serif",
        }}
      >
        <div style={{ fontSize: 32, opacity: 0.7 }}>stats47.jp</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div style={{ fontSize: 64, fontWeight: 700, lineHeight: 1.2 }}>
            {title}
          </div>
          <div style={{ fontSize: 28, opacity: 0.85 }}>{subtitle}</div>
        </div>
        <div style={{ fontSize: 24, opacity: 0.6 }}>
          47 都道府県統計データの可視化
        </div>
      </div>
    ),
    size,
  );
}

これだけで /charts/population-bar/opengraph-image という URL が立ち上がり、1200×630 の PNG が返ってきます。Twitter Card Validator に URL を投げて検証すれば、Claude Code に作らせた OG 画像が綺麗に出ます。

注意点 3 つ。

  • export const runtime = "edge" が必須。Cloudflare Pages では Edge Runtime のみ動く
  • 絶対パスのフォントファイルは読めない。日本語フォントを使うときは fetch で取得して ImageResponsefonts オプションに渡す(後述)
  • emoji は別途処理 が要る。Satori は emoji を CSS background-image でしか扱えない

日本語フォントを使いたい場合は、Google Fonts の Noto Sans JP の subset を R2 に置き、ビルド時に fetch する設計が安定します。

const font = await fetch(
  `${process.env.NEXT_PUBLIC_BASE_URL}/fonts/NotoSansJP-Bold-subset.woff`,
).then((r) => r.arrayBuffer());

return new ImageResponse(/* ... */, {
  ...size,
  fonts: [{ name: "Noto Sans JP", data: font, weight: 700, style: "normal" }],
});

Step 4: robots.ts と sitemap.ts の noindex 制御

新規ページを追加するときに 絶対に忘れてはいけない のがインデックス制御。stats47 は過去にここで大事故を起こしました(後述「つまずきポイント」参照)。

App Router では robots.tssitemap.ts をルート直下に置けば、それぞれ /robots.txt/sitemap.xml が自動生成されます。

// apps/web/src/app/robots.ts
import type { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: "*",
        allow: "/",
        disallow: [
          "/api/",
          "/admin/",
          "/*/opengraph-image", // ← 重要: OG 画像 URL は noindex
          "/_next/",
        ],
      },
    ],
    sitemap: "https://stats47.jp/sitemap.xml",
  };
}

/*/opengraph-image の Disallow を入れ忘れると、Google が OG 画像 URL を「ページ」として認識し、GSC に大量の 「クロール済み - インデックス未登録」 が積み上がります。stats47 では 2026 年に 1,453 件発生して気付きました(CLAUDE.md にも警告として書いてあります)。

// apps/web/src/app/sitemap.ts
import type { MetadataRoute } from "next";

const CHART_SLUGS = [
  "population-bar",
  "aging-heatmap",
  "medical-cost-choropleth",
  // ...
];

export default function sitemap(): MetadataRoute.Sitemap {
  const now = new Date();
  return [
    {
      url: "https://stats47.jp/",
      lastModified: now,
      changeFrequency: "weekly",
      priority: 1.0,
    },
    ...CHART_SLUGS.map((slug) => ({
      url: `https://stats47.jp/charts/${slug}`,
      lastModified: now,
      changeFrequency: "monthly" as const,
      priority: 0.8,
    })),
  ];
}

サイトマップに OG 画像 URL を含めない(Disallow と矛盾するため)。新規ページを追加するときは 必ず sitemap.ts にも追記 する。これを忘れると Google にいつまでも発見されません。

Claude Code に「apps/web/src/app/charts/[slug]/page.tsx を新規作成」と頼むときは、プロンプトに「同時に sitemap.ts に追記して」と書く のがチェックリスト化のコツ。

Step 5: Cloudflare Pages デプロイ

ビルドとデプロイは @opennextjs/cloudflare 経由でやります。next build の出力を Cloudflare Workers 用にトランスパイルしてくれるアダプタです。

# package.json の scripts
{
  "scripts": {
    "build": "next build",
    "build:cf": "next build && opennextjs-cloudflare build",
    "preview:cf": "opennextjs-cloudflare preview",
    "deploy": "opennextjs-cloudflare deploy"
  }
}

ローカルで本番ビルドを確認するなら npm run preview:cf。これは Cloudflare の wrangler pages dev を内部で叩き、本番に限りなく近い環境でブラウザ確認できます。

デプロイ自体はこの 1 行。

npx wrangler pages deploy .open-next/assets \
  --project-name stats47 \
  --branch main

CI でやるなら .github/workflows/deploy.yml を組みます。

name: Deploy to Cloudflare Pages
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run build:cf
        working-directory: apps/web
        env:
          STATS_DATA_BUCKET: ${{ secrets.STATS_DATA_BUCKET }}
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy apps/web/.open-next/assets --project-name=stats47 --branch=main

CLOUDFLARE_API_TOKEN は Cloudflare ダッシュボード → My Profile → API Tokens で発行。「D1 Edit + R2 Storage Edit + Pages Edit + Account Settings Read」 の 4 つで足ります(stats47 では「stats47」という 1 個のトークンに集約しています)。

初回デプロイのチェックリストはこちら。

#項目確認方法
1wrangler.tomlname がプロジェクト名と一致wrangler whoami
2R2 バケットが Cloudflare 上に作成済みダッシュボード → R2
3_routes.json で Workers と静的ファイルが正しく振り分け.open-next/_routes.json を grep
4環境変数(NEXT_PUBLIC_BASE_URL 等)が Cloudflare Pages 側にも登録ダッシュボード → Pages → Settings → Environment variables
5カスタムドメインの DNS が Cloudflare に向いているdig stats47.jp で Cloudflare の IP が返る
6デプロイ後の URL で /robots.txt /sitemap.xml が 200 を返すcurl -I https://stats47.jp/robots.txt

ここまで通れば、本番に出ています。https://stats47.jp/charts/population-bar を開いてチャートが描画されたら、シリーズ累計 20 本の集大成が世に出た瞬間 です。

Step 6: Analytics と Search Console 設定

公開 = 完了ではない。どれだけ見られているかを計測 する仕組みを入れて初めて運用が始まります。

Google Analytics 4

GA4 は Next.js の next/scriptafterInteractive で読み込むだけ。Cloudflare Pages でも普通に動きます。

// apps/web/src/app/layout.tsx
import Script from "next/script";

const GA_ID = process.env.NEXT_PUBLIC_GA_ID;

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja">
      <body>
        {children}
        {GA_ID && (
          <>
            <Script
              src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`}
              strategy="afterInteractive"
            />
            <Script id="ga-init" strategy="afterInteractive">
              {`
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());
                gtag('config', '${GA_ID}', { send_page_view: true });
              `}
            </Script>
          </>
        )}
      </body>
    </html>
  );
}

Consent Mode を使うなら gtag('consent', 'default', ...) を先に。stats47 では Consent Mode v2 を入れていますが、今回は割愛します。

Google Search Console

GSC への登録は 2 ステップ。

  1. ドメイン所有権の確認: DNS TXT レコードで認証。Cloudflare DNS なら 1 分で完了
  2. サイトマップの送信: GSC の「サイトマップ」メニューに https://stats47.jp/sitemap.xml を貼り付ける

サイトマップ送信から 2-7 日で初期インデックス が走ります。GSC の「ページ」レポートに「インデックスに登録済み」が増えてくれば成功。

/charts/[slug] が GSC で 「クロール済み - インデックス未登録」 になっている場合は、(a) コンテンツが薄い、(b) noindex が誤って付いている、(c) 内部リンクが少ない、のいずれか。Part 1-19 までの記事から該当チャートへ内部リンクを張れば、ほとんどは解消します。

つまずきポイント

連載 20 本の中で stats47 が実際に踏んだ地雷を 4 つ。

1. cookies() / headers() in layout で SSG 崩壊

最大の地雷apps/web/src/app/layout.tsx または layout 配下の Server Component で cookies()headers() を呼ぶと、全ページが SSG から外れて Dynamic Rendering になります。Cloudflare Pages では Edge Worker が毎リクエスト発火するので、(a) TTFB が劇的に悪化、(b) Workers 無料枠を圧迫、(c) Lighthouse スコアが 90 → 50 に転落、という三重苦が起きます。

stats47 では Experiment EXP-004 / EXP-005 で 2 回踏んだので、.claude/rules/nextjs-ssg-preservation.md に「Server Component で cookies/headers を呼ぶな」と明文化してあります。Cookie が必要なら Client Component に閉じ込めて document.cookie を読む、もしくは Route Handler 経由で取る、の 2 択。

Claude Code に「ヘッダーに今日の日付を表示して」と頼んだら勝手に headers() を呼んできた——ということもあるので、生成されたコードを必ず grep

grep -rn "from \"next/headers\"" apps/web/src/app/layout.tsx \
  apps/web/src/components/ -l

2. Edge Runtime の制約

Cloudflare Pages では Node.js 標準ライブラリの大半が動きませんfs path crypto(一部)stream(一部)が NG。opengraph-image.tsxruntime = "edge" を指定するのは、これに合わせる必要があるからです。

@opennextjs/cloudflare がいくつかの Node API を polyfill してくれますが、ビルド時 fetch でデータを取り込む か、Edge 互換のライブラリだけ使う のが安全です。p-limit zod date-fns あたりは Edge OK。sharp(画像処理)puppeteer は NG。

3. Image 最適化と Cloudflare の罠

Next.js の <Image> コンポーネントは Vercel の Image Optimization 前提で設計されています。Cloudflare Pages では next.config.tsimages.unoptimized: true にするか、Cloudflare Images(有料)を使う必要があります。

stats47 では、(a) OG 画像 → opengraph-image.tsx で都度生成、(b) サムネイル → 事前に WebP 化して R2 配置、(c) 通常の <img> → 直接配信、の 3 段構えで next/image を一切使っていません。Edge 環境で画像最適化を頑張るより、事前に最適化済みアセットを R2 に置く ほうが運用が楽。

4. SSG 時の fetch エラーがビルドを止める

generateStaticParams 中の R2 fetch が失敗すると、ビルド全体がコケます。snapshot がまだ生成されていない slug を返してしまうと、generateMetadatafetchChartData が null を返し、ビルドエラーに。

対策:

export async function generateStaticParams() {
  const slugs = await listAvailableSlugs(); // R2 にデータが存在するものだけ
  return slugs.map((slug) => ({ slug }));
}

async function listAvailableSlugs(): Promise<string[]> {
  const { env } = getCloudflareContext();
  const list = await env.STATS_DATA_BUCKET.list({
    prefix: "app/charts/",
    delimiter: "/",
  });
  return list.delimitedPrefixes
    .map((p) => p.replace("app/charts/", "").replace("/", ""))
    .filter(Boolean);
}

R2 をビルド時に list して 存在するものだけ静的生成 する。これで「データが揃わないとビルドできない」というフラジリティが消えます。

デプロイ図

連載のチャートが本番に出るまでの流れを図にすると、こうなります。

[ローカル: Claude Code セッション] ↓ /fetch-estat-data で JSON 取得 [ローカル: snapshot 生成スクリプト] ↓ wrangler r2 object put [Cloudflare R2: app/charts/[slug]/data.json] ↓ git push origin main [GitHub: PR merge → workflows/deploy.yml] ↓ npm run build:cf [Cloudflare Pages ビルダー: Next.js build + opennextjs-cloudflare] ↓ R2 から fetch して静的 HTML 生成 [Cloudflare CDN: 200+ エッジロケーション] ↓ ユーザーリクエスト [stats47.jp/charts/population-bar]

URL 構造も明示しておくと、Claude Code に追加ページを頼むときに迷子になりません。

stats47.jp/ ├── / # トップ ├── /charts # チャート一覧 ├── /charts/[slug] # 個別チャート(15 種) │ ├── /charts/population-bar │ ├── /charts/aging-heatmap │ └── ... ├── /blog/[slug] # ブログ記事 ├── /ranking/[rankingKey] # ランキング ├── /category/[categoryKey] # カテゴリ ├── /areas/[areaCode] # 都道府県別 ├── /robots.txt # robots.ts から生成 └── /sitemap.xml # sitemap.ts から生成

シリーズまとめ — 何が変わるか

連載 20 本を貫いて主張してきたのは 「Claude Code は試行錯誤の速度を 1 桁変える」 という 1 点に尽きます。

工程手書きスクリプト時代Claude Code 時代
e-Stat 統計表の探索ブラウザ + メモ 30 分/search-estat で 1 分
データ取得スクリプト1-2 時間プロンプト 30 秒 + 実行 1 分
チャート設計D3 ドキュメント 2 時間「散布図にして」で 5 分
エラー対応スタックトレース読解エラー貼り付けて自動修正
キャッシュ実装半日Part 18 のレシピで 30 分
テスト1 日Part 19 で 1 時間
デプロイ2-3 日本記事の手順で 2 時間
合計(チャート 1 本)約 1 ヶ月約 1 日

「1 ヶ月 → 1 日」が単なる時短ではなく、「やる気が続いている間に完結する」 という意味で本質的です。エンジニアの個人開発で頓挫する最大の理由は「途中で飽きる」「土日が潰れて気力が削られる」。Claude Code はこれを 「思いついたその日に公開まで持っていける」 スピード感にします。

20 本を通して見てきた Claude Code 活用の 5 つの原則 を最後にまとめます。

  1. 頻出処理はスキル化 — Part 2 の /search-estat のように、何度も使うものは .claude/skills/ に固定
  2. データ取得とチャート描画を分離 — Part 18 の R2 キャッシュで取得層を独立させる
  3. Server Component で fetch、Client Component で描画 — Next.js App Router の原則
  4. SEO 制御は最初から仕込む — robots.ts / sitemap.ts / OG 画像はページ作成と同時に
  5. ビルドエラーで止まらない設計 — R2 list で存在するものだけ静的生成

これさえ守れば、Claude Code は 「47 都道府県の任意の統計指標を、思いついたその日に Web 公開できるパートナー」 になります。stats47.jp 自体が、その実証実験の 1 年分の成果です。

連載完走バッジ — Part 1 〜 Part 19 まとめ

ここまで読み切ってくれた方、本当にお疲れさまでした。シリーズ全 20 本のリンクをまとめて置いておきます。途中の Part を飛ばしている人は、興味のあるところから戻ってもらえれば。

環境・基盤編

チャート作成編(15 本)

運用・最適化編

公開編(本記事)

  • Part 20 Next.js + Cloudflare Pages デプロイ ← いまここ

関連ランキング・記事

連載完走後にチェックして欲しい stats47 内のページもどうぞ。


連載シリーズはここで一旦完結します。次のテーマは未定ですが、(a) Cloudflare D1 と Drizzle ORM で 47 都道府県のリレーショナル分析、(b) Remotion で統計データを動画化して YouTube に上げる、(c) Claude Code で SEO 改善を回す あたりを検討中。リクエストがあれば stats47 の問い合わせフォームからどうぞ。

20 本完走、お疲れさまでした。Claude Code と e-Stat の組み合わせで、あなたの「気になる 47 都道府県データ」もぜひ公開してみてください。claude と打って、開発の楽しさを思い出す——それが本連載で一番伝えたかったことです。

それでは、また次の連載で。