Vercel × Nuxt 4 × microCMSで実現するモダン・JamstackなWEBサイト構築 - 実運用から見えた技術選定の勘所と最適化戦略

Vercel × Nuxt 4 × microCMSで実現するモダン・JamstackなWEBサイト構築 - 実運用から見えた技術選定の勘所と最適化戦略

エンジニアブログ
最終更新日:2025年08月28日公開日:2025年08月28日
益子 竜与志
writer:益子 竜与志
XThreads

コーポレートサイトのリニューアルプロジェクトにおいて、「Jamstack」アーキテクチャの採用は今や定番の選択肢となりました。しかし、実際に本番環境で運用してみると、華々しい技術スタックの裏側には想定外のコスト構造や運用上の落とし穴が潜んでいることに気づきます。

自社サイトのリニューアルを通じて得た実践的な知見を基に、Vercel、Nuxt 4、microCMSという技術スタックの真の価値と、運用フェーズで直面する課題への対処法を詳しく解説します。特に「SaaSのコスト構造」と「開発生産性」のバランスをどう取るかという観点から、エンタープライズ企業やスタートアップのエンジニアリング組織が押さえるべきポイントを整理しました。

モダンなWebアーキテクチャ選定における本質的な価値判断

従来型インフラからJamstackへの移行で得られるもの

これまでのSSR(サーバーサイドレンダリング)を実現しようとすると、AWS ECS、CloudFront、CodePipelineといった「フルスタック」なインフラ構成が必要でした。確かに柔軟性は高いものの、コーポレートサイトという規模感に対して明らかにオーバースペックであり、運用コストも相応にかかっていました。

Jamstackアーキテクチャの採用により、インフラ管理の負荷を劇的に削減しながら、高速なサイトパフォーマンスとスケーラビリティを同時に実現できます。特にVercelのようなエッジプラットフォームは、「デプロイの簡単さ」と「運用の手離れの良さ」において従来のインフラ構成を圧倒的に凌駕します。

ただし、この移行は単なる技術的な置き換えではありません。「ヘッドレスCMS」の採用による編集体験の変化、APIベースのコンテンツ管理によるコスト構造の変化など、組織全体で理解しておくべき変化点が存在します。

技術スタック選定における三つの評価軸

今回のプロジェクトでは、以下の三つの評価軸を重視して技術選定を行いました。

「開発生産性」の観点では、Git連携によるCI/CD自動化、プレビュー環境の自動生成、型安全な開発体験などが重要です。「運用効率」では、コンテンツ編集者の学習コスト、更新作業の効率性、障害対応の容易さを評価します。「総所有コスト(TCO)」については、初期開発費用だけでなく、月額利用料、データ転送量による従量課金、将来的な拡張時のコストまで含めた総合的な判断が必要です。

これらの軸で評価した結果、Vercel × Nuxt 4 × microCMSの組み合わせが最適解となりましたが、その判断に至るまでの詳細な検討内容を以下で共有します。

Vercelが変えるフロントエンド開発の風景

プレビュー環境の革新的な運用体験

https://vercel.com/docs/deployments/generated-urls?utm_source=chatgpt.com
https://vercel.com/docs/deployments/generated-urls?utm_source=chatgpt.com

Vercelのプレビューデプロイメント機能は、開発チームのコラボレーションを根本的に変える力を持っています。Gitのブランチをプッシュするだけで、自動的にユニークなURLが生成され、その環境で実際の動作を確認できます。

特筆すべきは、フィーチャーブランチごとに独立した環境が自動生成される点です。デザイナーやプロダクトマネージャーが、ローカル環境を立ち上げることなく実装状況を確認できるため、フィードバックサイクルが劇的に短縮されました。

実運用では、デプロイ保護機能を活用してプレビュー環境へのアクセスを制限しています。Vercel認証による保護と、x-vercel-protection-bypassヘッダーを使用したAPIアクセスのバイパス設定により、セキュアな環境を維持しながら自動テストやWebhook連携も問題なく動作させることができました。

Nuxtサーバーミドルウェアとの統合における実践的な解決策

https://nuxt.com/docs/4.x/guide/directory-structure/app/middleware
https://nuxt.com/docs/4.x/guide/directory-structure/app/middleware

Nuxt 4のサーバーミドルウェアをVercel上で動作させる際、認証周りで予期しないハマりポイントがありました。具体的には、サーバーサイドからAPIを呼び出す際に、Vercelの認証がブロックしてしまう問題です。

この問題は、内部APIリクエストに対して適切なバイパスヘッダーを付与することで解決できました。以下のようなアプローチで、セキュリティを保ちながら内部通信を実現しています。

// server/api/internal-fetch.ts
export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()

  // Vercel認証をバイパスするヘッダーを付与
  const headers = {
    'x-vercel-protection-bypass': config.vercelBypassToken,
    'Content-Type': 'application/json'
  }

  // 内部APIへのリクエスト
  const response = await $fetch('/api/data', {
    headers,
    baseURL: config.public.siteUrl
  })

  return response
})

このような実装により、プレビュー環境でも本番環境と同等の動作を実現しながら、適切なセキュリティレベルを維持できています。

Slack連携による開発フローの最適化

VercelのSlack連携機能により、デプロイ通知やプレビューURLの共有が自動化されました。これにより、チーム全体がデプロイメントの状況をリアルタイムで把握でき、コミュニケーションコストが大幅に削減されています。

特に効果的だったのは、デプロイ完了通知にプレビューURLが自動的に含まれる点です。PRレビュー時に、レビュアーがSlack通知から直接プレビュー環境にアクセスできるため、コードレビューと実装確認を並行して効率的に進められます。

Nuxt 4の進化と実装における最適化戦略

ハイブリッドレンダリングによるパフォーマンス最適化

Nuxt 4のrouteRules機能は、ページごとに最適なレンダリング戦略を選択できる強力な仕組みです。実際のコーポレートサイトでは、以下のような戦略でパフォーマンスとコストの最適化を実現しました。

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // トップページ:ISRで5分ごとに再生成
    '/': { isr: 300 },

    // ニュース一覧:SWRで1分キャッシュ
    '/media//news': { swr: 60 },

    // ニュース詳細:ISRで10分ごとに再生成
    '/media/news/**': { isr: 600 },

    // 会社概要など静的ページ:ビルド時に事前生成
    '/company': { prerender: true },
    '/contact': { prerender: true }
  }
})

この設定により、更新頻度の高いコンテンツは適切な間隔で再生成しつつ、静的なページは事前生成することでAPIコールを削減しています。結果として、microCMSのAPI呼び出し回数を約70%削減し、転送量も大幅に抑制できました。

SEO最適化とメタデータ管理の実践

Nuxt 4のuseSeoMetaを活用することで、型安全なSEO管理を実現しています。SSRと組み合わせることで、初回HTMLレスポンスに適切なメタタグが含まれるため、検索エンジンのクローラビリティが向上します。

実装例として、ブログ記事ページでの動的なメタデータ設定を以下に示します。

// pages/blog/[slug].vue
<script setup lang="ts">
const route = useRoute()
const { data: article } = await useFetch(`/api/blog/${route.params.slug}`)

// 型安全なSEOメタ設定
useSeoMeta({
  title: () => `${article.value?.title} | 株式会社○○`,
  description: () => article.value?.description,
  ogTitle: () => article.value?.title,
  ogDescription: () => article.value?.description,
  ogImage: () => article.value?.thumbnail?.url,
  ogType: 'article',
  articlePublishedTime: () => article.value?.publishedAt,
  articleModifiedTime: () => article.value?.updatedAt,
  articleAuthor: () => article.value?.author?.name
})
</script>

このアプローチにより、各ページで適切な構造化データを自動生成し、検索エンジンでの表示最適化を実現しています。

TypeScript完全対応による開発体験の向上

Nuxt 4では、TypeScriptサポートがさらに強化されました。自動型生成機能により、APIレスポンスやルートパラメータの型が自動的に推論され、開発時のミスを大幅に削減できます。

特に、microCMSから取得するコンテンツの型定義を自動生成し、フロントエンドで活用する仕組みを構築しました。

Nuxt4のサーバーAPIのVercel Functionsホスティング戦略

Nuxt4のサーバーミドルウェアを使用すると、Vercelデプロイ時に自動的にVercel Functionsにデプロイされるので超便利です。以下に実装内容を紹介します!(実際これで動いてますー)尚、APIKEYなどの秘匿情報はVercel上の環境変数にて保持しています。

MicroCMSからコンテンツを取得するNuxt4サーバーAPI

// server/api/cms/[...path].ts
import { defineEventHandler, getQuery, createError } from "h3";
import { getRouterParams } from "h3";

export default defineEventHandler(async (event): Promise<Json> => {
  try {
    const { microcmsBaseUrl, microcmsApiKey } = useRuntimeConfig();

    if (!microcmsBaseUrl || !microcmsApiKey) {
      throw createError({
        statusCode: 500,
        statusMessage: "microCMS configuration is missing on the server",
      });
    }

    const params = getRouterParams(event) as { [key: string]: string | string[] | undefined };
    const raw = params?.path;
    // Ensure catch-all param is split into path segments when provided as a single string like "news/abc123"
    const segments = Array.isArray(raw)
      ? raw
      : typeof raw === "string"
        ? raw.split("/").filter(Boolean)
        : [];

    if (segments.length === 0) {
      throw createError({ statusCode: 400, statusMessage: "Invalid CMS path" });
    }

    const endpointPath = `/${segments.map((s) => encodeURIComponent(s)).join("/")}`;

    const query = getQuery(event) as Record<string, string | string[] | number | boolean | undefined>;

    const result = await $fetch<Json>(endpointPath, {
      baseURL: microcmsBaseUrl,
      method: "GET",
      headers: {
        "X-MICROCMS-API-KEY": microcmsApiKey,
      },
      params: query,
    })

    return result;

  } catch (error: any) {
    console.error("microCMS fetch error:", error);

    // Map upstream microCMS status to appropriate responses to avoid soft-404.
    const status =
      (error?.response && typeof error.response.status === "number" && error.response.status) ||
      (typeof error?.statusCode === "number" && error.statusCode) ||
      undefined;

    if (status === 404) {
      throw createError({
        statusCode: 404,
        statusMessage: "Not Found",
        cause: error,
      });
    }

    if (status === 400) {
      throw createError({
        statusCode: 400,
        statusMessage: "Bad Request",
        cause: error,
      });
    }

    // Default: upstream failure
    throw createError({
      statusCode: 502,
      statusMessage: "Failed to fetch data from microCMS",
      cause: error,
    });
  }
});

フォーム送信時の通知

// server/api/mail/contact.post.ts
import { defineEventHandler, readBody, createError } from "h3";
import { processNotification } from "../../lib/notifications";

export default defineEventHandler(async (event) => {
  try {
    const input = await readBody<{
      companyName: string;
      personName: string;
      email: string;
      body: string;
      type: string;
      subject: string;
    }>(event);

    if (!input || !input.email || !input.subject) {
      throw createError({ statusCode: 400, statusMessage: "Missing required fields" });
    }

    const { companyName, personName, email, body, type, subject } = input;

    const htmlBody = `
<p>この度は、Ragate株式会社へお問い合わせいただきありがとうございます、</p>
<p>担当より3営業日以内にご連絡を差し上げます。</p>
<p>【御社名/氏名】</p>
<p>${companyName}/${personName}</p>
<p>【メールアドレス】</p>
<p>${email}</p>
<p>【種別】</p>
<p>${type}</p>
<p>【お問い合わせ内容】</p>
<p>${body}</p>
`;

    const sourceMailAddress = process.env.SES_SOURCE_CONTACT_CORP || "info@ragate.co.jp";
    const slackWebhookUrl = process.env.SLACK_WEBHOOK_CONTACT_CORP;

    await processNotification({
      htmlBody,
      email,
      subject,
      sourceMailAddress,
      slackWebhookUrl,
    });

    return { ok: true };
  } catch (error: any) {
    console.error("send contact error:", error);
    const status =
      (error?.response && typeof error.response.status === "number" && error.response.status) ||
      (typeof error?.statusCode === "number" && error.statusCode) ||
      500;

    throw createError({
      statusCode: status,
      statusMessage: status === 400 ? "Bad Request" : "Failed to send contact inquiry",
      cause: error,
    });
  }
});

microCMSの実運用で見えてきたコスト構造の真実

API数とプラン選択の現実的な判断基準

microCMSの料金体系を実際に運用してみると、「API数」という概念が想像以上にコストドライバーとなることがわかりました。各コンテンツモデル(ブログ関連、ニュース、採用コンテンツなど)がそれぞれ1APIとカウントされるため、一般的なオウンドメディア構成でも容易に10本を超えてしまいます。

実際のプロジェクトでは、以下のようなAPI構成となりました。

表 実プロジェクトにおけるmicroCMS API構成と用途

API名

用途

更新頻度

エンジニアブログ

技術ブログ

週2-3回

ニュース

お知らせ・プレスリリース

月2回

支援実績

お客様への支援実績

月2回

著者(執筆者)

執筆者プロフィール

月1回

求人・募集ポジション

求人記事

月1回

ましこ(社長)ブログ

代表者の情報発信

隔週1回

カルチャー

会社のカルチャー

月1回

認定・受賞

会社の認定・受賞歴

年10回

働くヒトの声

社員の声

月1回

サービス

提供しているサービス

月1回

パブリック社内報

社内の活動

月2回

この構成では、すでにTeamプランの上限である10APIに達しています。API追加料金(2,000円/本)が発生するか、Businessプラン(月額75,000円)への移行を検討する必要があります。(リリース時点では一旦てAPIの追加料金を支払い対応、今後ビジネスプラン検討へ)

データ転送量の落とし穴と最適化アプローチ

microCMSのデータ転送量は、JSON取得と画像配信の合計でカウントされます。特に画像リッチなメディアサイトでは、この転送量が予想以上に膨らむケースがありますが、今回はVercelによるキャッシュを有効化することでMicroCMSへの問い合わせの回数を削減しているため、気にするほどではありません。

Teamプランの基本転送量200GB/月に対して、実際の利用量は200GB未満であると想定していますが、ベンチマークを見ながら以下の対策を追って検討しようと思います。

画像圧縮

転送量削減のための具体的な施策として、まず画像の最適化があげられます。Vercel CDNにスクリプトやエコシステムを設定することで、自動的にWEBP形式に変換したり、画像の圧縮提供が可能となります。

また、レスポンシブ画像の実装によりデバイスに応じた適切なサイズの画像を配信し、Next/Imageコンポーネントを活用した自動最適化も検討可能です。加えてVercelの提供するCDNを併用することで、オリジン(MicroCMS)の負荷がすると考えています。

API呼び出しのキャッシュ

API呼び出しの最適化では、VercelファンクションとNuxt4のキャッシュ機能を最大限活用しました。routeRulesによる適切なキャッシュ戦略の設定と、$fetchのキャッシュオプション活用により、重複したAPI呼び出しを削減します。

WordPressとの実質的なコスト比較

従来のWordPress運用と比較すると、コスト構造が大きく異なることがわかります。WordPress.org自体は無償のOSSですが、実際の運用には以下のようなコストが発生します。

表 WordPress vs microCMS 年間TCO比較(中規模サイト想定)

コスト項目

WordPress(自社運用)

microCMS + Vercel

初期構築費

10万円

※開発に関する労務費など

無料(ベンダー依頼無)

ホスティング

AWSのリソース利用費として月2万円程度(年30万円)

※LightSailのBitnamiが手軽

無料(SaaSの為)

CMS利用料

0円

Vercel Pro 月$40(年7万円)

※ミニマムに2名で利用

保守・運用

年2万円程度
※アップグレードや最新版へのマイグレーション対応などの工数換算

無料(SaaSの為)

セキュリティ対策

月5千円(年6万円)

※ AWS WAFの利用費 (OASP Top 10対策等 )

無料(SaaSの為)

年間総コスト

38万円

※ 初期費用を含まない

7万円

単純な金額比較では、小規模なサイトであればmicroCMSが有利ですが、API数が増え、Businessプランが必要になると、コスト差は縮まります。ただし、セキュリティアップデートやインフラ管理の手間を考慮すると、SaaSの価値は金額以上のものがあります。(初期費用においてはWordpressのPHPをゴニョゴニョしないで済むのでMicroCMSのGUIは魅力的)

実装における具体的なベストプラクティス

Webhook連携による自動デプロイの実装

microCMSのWebhook機能VercelのDeploy Hooksを連携させることで、コンテンツ更新時の自動デプロイを実現しました。

設定は極めてシンプルで、microCMSの管理画面でWebhook URLにVercelのDeploy Hook URLを指定するだけです。これにより、記事の公開・更新・削除のタイミングで自動的に再ビルドがトリガーされ、常に最新のコンテンツが反映されます。

ただし、頻繁な更新がある場合は、ビルド回数の制限に注意が必要です。Vercelの無料プランでは月6,000分のビルド時間制限があるため、不要なWebhookトリガーを避ける工夫が必要です。

キャッシュ戦略の実践的な設計

効果的なキャッシュ戦略は、パフォーマンスとコストの両面で重要です。実プロジェクトでは、コンテンツの特性に応じて以下のような階層的なキャッシュ戦略を採用しました。

エッジキャッシュレベルでは、Vercelのエッジネットワークを活用し、静的アセットは長期間キャッシュ(1年)、動的コンテンツは短期間キャッシュ(1-5分)を設定しています。アプリケーションレベルでは、NuxtのrouteRulesによるISR/SWR設定と、useFetchのキャッシュオプションを併用しています。

ブラウザキャッシュでは、適切なCache-Controlヘッダーの設定により、不要なリクエストを削減しています。特に画像などの静的リソースは、積極的にブラウザキャッシュを活用することで、ユーザー体験の向上とサーバー負荷の軽減を同時に実現しています。

エラーハンドリングとモニタリング

本番環境では、適切なエラーハンドリングとモニタリングが不可欠です。Vercelの機能を活用しつつ、以下のような監視体制を構築しました。

// plugins/error-handler.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    // Sentryなどのエラー監視サービスに送信
    if (process.env.NODE_ENV === 'production') {
      console.error('Vue error:', error)
      // エラー情報を収集・送信
      sendErrorToMonitoring({
        error: error.message,
        stack: error.stack,
        component: instance?.$options.name,
        info
      })
    }
  }

  // APIエラーのグローバルハンドリング
  nuxtApp.hook('app:error', (error) => {
    console.error('Nuxt app error:', error)
    // ユーザーフレンドリーなエラー表示
    showErrorNotification('申し訳ございません。エラーが発生しました。')
  })
})

また、Vercel Analyticsを活用してCore Web Vitalsをモニタリングし、パフォーマンスの継続的な改善を行っています。

運用フェーズで得られた実践的な知見

チーム開発における効率化のポイント

実際の運用を通じて、以下のような開発フローが最も効率的であることがわかりました。

フィーチャーブランチ開発では、各機能開発を独立したブランチで行い、Vercelの自動プレビューURLを活用してレビューを実施します。プレビュー環境の共有により、非エンジニアメンバーも早期にフィードバックを提供でき、手戻りを最小限に抑えられます。

コンテンツ編集フローでは、microCMSの下書き機能を活用し、プレビューAPIを通じて公開前の確認を行います。承認フローを設定することで、誤った公開を防ぎつつ、スムーズな更新作業を実現しています。

スケーラビリティの考慮事項

将来的な拡張を見据えて、以下の点に注意を払っています。

API設計の拡張性では、初期段階から適切な情報設計を行い、将来的なAPI追加を最小限に抑える工夫をしています。関連性の高いコンテンツは、できるだけ同一API内でフィールドとして管理し、不要なAPI分割を避けています。

パフォーマンスのスケーラビリティでは、アクセス増加に対してVercelのエッジネットワークが自動的にスケールするため、インフラ面での心配は不要です。ただし、microCMSのAPI制限(リクエスト数/秒)には注意が必要で、キャッシュ戦略の最適化が重要になります。

移行プロジェクトで押さえるべきポイント

既存サイトからの移行を検討している組織に向けて、実体験から得られた重要なポイントを共有します。

段階的な移行戦略として、すべてを一度に移行するのではなく、静的ページから順次移行する方法が効果的です。まずは会社概要などの更新頻度の低いページから始め、徐々にダイナミックなコンテンツへと移行範囲を広げていきます。

コンテンツ移行の自動化では、既存CMSからのデータエクスポートとmicroCMSへのインポートを自動化するスクリプトを作成し、手作業を最小限に抑えます。特に画像URLの変換やリンクの修正など、機械的に処理できる部分は積極的に自動化します。

リダイレクト設定の重要性も見逃せません。SEOの観点から、旧URLから新URLへの適切なリダイレクト設定が不可欠です。Vercelのredirects設定を活用し、301リダイレクトを確実に実装します。

今後の展望と技術トレンド

Edge Functionsの活用可能性

Vercel Edge Functionsの進化により、エッジでの処理がさらに高度化しています。今後は、以下のような用途での活用を検討しています。

パーソナライゼーションでは、ユーザーの地域や属性に応じたコンテンツの出し分けをエッジで実現し、レスポンスタイムを最小化します。A/Bテストの実装においても、エッジレベルでのトラフィック分割により、パフォーマンスへの影響を最小限に抑えながら、効果的なテストを実施できます。

AI・機械学習との統合

生成AIの活用が一般化する中、コンテンツ管理システムとAIの統合も重要なテーマとなっています。

コンテンツの自動生成では、定型的な記事の下書き作成や、メタディスクリプションの自動生成などにAIを活用することで、編集者の負担を軽減できます。画像の自動最適化においても、AIを活用した自動クロッピングや、alt属性の自動生成により、アクセシビリティの向上と作業効率化を同時に実現できます。

まとめ:技術選定における本質的な価値

Vercel × Nuxt 4 × microCMSという技術スタックは、単なるトレンドの追求ではなく、実務における具体的な価値を提供します。

開発生産性の観点では、Git連携による自動化、型安全な開発体験、優れた開発者体験により、開発スピードが大幅に向上します。運用効率の面では、インフラ管理からの解放、セキュリティアップデートの自動化、直感的なコンテンツ管理により、運用負荷が劇的に削減されます。

ただし、microCMSのコスト構造には注意が必要で、特にAPI数と転送量は事前に慎重な見積もりが必要です。小規模なサイトでは圧倒的にコストメリットがありますが、大規模化するとBusinessプランへの移行が避けられず、その際のコストジャンプは無視できません。

それでも、総合的に判断すると、このスタックが提供する価値は、コストを上回るものがあります。特に、限られたリソースで最大の成果を出したいスタートアップや、DXを推進したいエンタープライズ企業にとって、検討に値する選択肢といえるでしょう。

技術選定は、単純な機能比較やコスト比較だけでなく、組織の成熟度、チームのスキルセット、将来的な拡張性など、多角的な視点から判断する必要があります。本記事で共有した実践的な知見が、皆さんの技術選定の一助となれば幸いです。

Careerバナーconsultingバナー