DEV Community

shreyas shinde
shreyas shinde

Posted on • Originally published at kanaeru.ai on

私たちのSEOジャーニー:SPAからNext.jsへ(完全攻略ガイド)

SEOジャーニー:「クロール済み - インデックス未登録」から検索可視性へ
SEOジャーニー:赤いXマークの未インデックスページから、緑のチェックマークとGoogle承認のインデックス済みページへ

美しいSingle Page Application(SPA)を構築することは一つのこと。Googleに実際にインデックスさせることは、まったく別の課題です。

これは、検索エンジンが適切にインデックスできなかったクライアントサイドレンダリングのReactアプリから、包括的なSEOを備えたNext.jsサイトへと変革した物語です。

問題:美しいが見えない

マーケティングWebサイトを最初にローンチする際、私たちはLovable.devを出発点として選びました。Lovableは内部でVite + Reactを使用しており、洗練されたベーステンプレートと迅速な初期開発速度を提供してくれました。私たちはLovableのAIインターフェースを通じてサイト全体をデザインし、その後コードをGitHubに移行して、Claude Codeで完全に開発を継続しました。

結果は人間の訪問者にとって完璧に見えました。アニメーションは滑らかで、デザインは洗練されており、コンテンツは魅力的でした。

しかし問題がありました: Googleにはほとんど見えていなかったのです。

Google Search Consoleは苛立たしいパターンを示していました:

  • 「クロール済み - 現在インデックスに登録されていません」とマークされたページ
  • クローラーにホームページのHTMLを返すブログ記事
  • ページ間の重複コンテンツの問題
  • リッチスニペット用の構造化データの欠如

根本原因は?SPAはJavaScriptでコンテンツをレンダリングします。検索エンジンのクローラーは改善されていますが、JavaScript重視のページにはまだ苦労しています。Googlebotが私たちのブログ記事を訪問したとき、すべてのURLで同じ汎用ホームページHTMLが表示されていました。

7フェーズSEO最適化ジャーニー:基盤(10月)、インデックス修正(10月)、パフォーマンス(10月)、バックリンク(10-11月)、Ahrefs監査(12月)、Next.js移行(12月)、最終調整(12月) - 0から100へ

フェーズ1:基盤作業(2025年10月)

包括的なSEOインフラストラクチャ

最初の主要な修正は基本に対処しました:

1. サイトマップ生成

すべてのビルドで実行される動的サイトマップジェネレーターを作成しました:

// scripts/generate-sitemap.mjs
const routes = [
  { url: '/', changefreq: 'weekly', priority: 1.0 },
  { url: '/platform', changefreq: 'monthly', priority: 0.8 },
  { url: '/team', changefreq: 'monthly', priority: 0.7 },
  { url: '/blog', changefreq: 'daily', priority: 0.9 },
  // ... ブログ記事は動的に追加
];

Enter fullscreen mode Exit fullscreen mode

2. モダンクローラー向けrobots.txt

検索エンジンとLLMクローラーの両方を明示的に許可するようにrobots.txtを更新しました:

User-agent: Googlebot
Allow: /

User-agent: ChatGPT-User
Allow: /

User-agent: Claude-Web
Allow: /

User-agent: PerplexityBot
Allow: /

Sitemap: https://www.kanaeru.ai/sitemap.xml

Enter fullscreen mode Exit fullscreen mode

3. JSON-LD構造化データ

ホームページにOrganization、WebSite、Serviceスキーマを追加しました:

{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Kanaeru AI",
  "url": "https://www.kanaeru.ai",
  "logo": "https://www.kanaeru.ai/logo.png",
  "sameAs": [
    "https://github.com/kanaerulabs",
    "https://www.linkedin.com/company/kanaeru-ai"
  ]
}

Enter fullscreen mode Exit fullscreen mode

ブログ記事のプリレンダリング

ゲームチェンジャーは、ブログ記事の静的HTML生成を実装したことでした。すべてのリクエストに同じSPAシェルを提供する代わりに、各ブログ記事を以下の内容でプリレンダリングしました:

  • 完全なメタタグ(title、description、Open Graph、Twitter Cards)
  • クローラー向けの完全な記事コンテンツ
  • 適切なcanonical URL
  • BlogPosting JSON-LD構造化データ
// scripts/prerender-blog.ts
async function prerenderBlogPost(post: BlogPost) {
  const html = `
    <!DOCTYPE html>
    <html lang="${post.locale}">
    <head>
      <title>${post.title}</title>
      <meta name="description" content="${post.excerpt}">
      <link rel="canonical" href="https://www.kanaeru.ai/blog/${post.slug}">
      <script type="application/ld+json">
        ${JSON.stringify(generateBlogPostingSchema(post))}
      </script>
    </head>
    <body>
      <article>${post.htmlContent}</article>
    </body>
    </html>
  `;

  await writeFile(`public/prerendered/blog/${post.slug}.html`, html);
}

Enter fullscreen mode Exit fullscreen mode

フェーズ2:重大なインデックス問題の修正(2025年10月)

基盤作業の後も、まだ問題がありました。Google Search Consoleはブログ記事に「クロール済み - 現在インデックスに登録されていません」と表示していました。調査により、いくつかの問題が明らかになりました:

1. 間違ったCanonical URL

ブログ記事が自分自身のURLではなく、ホームページをcanonical URLとして指していました。これはGoogleに「私をインデックスしないで、代わりにホームページをインデックスして」と伝えていました。

修正: 各ページタイプに対して正しいcanonical URLを生成するようにSEOライブラリを更新しました。

2. BlogPostingスキーマの欠如

汎用のOrganizationスキーマでは不十分でした。ブログ記事には特定のBlogPosting構造化データが必要です:

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "記事タイトル",
  "datePublished": "2025-10-13",
  "dateModified": "2025-10-15",
  "author": {
    "@type": "Person",
    "name": "Shreyas Shinde"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Kanaeru AI"
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.kanaeru.ai/blog/article-slug"
  }
}

Enter fullscreen mode Exit fullscreen mode

3. 空の画像フィールド

Schema.orgは画像を必要とします。画像フィールドを空のままにしていたため、検証エラーが発生していました。

修正: 記事固有の画像が利用できない場合にデフォルト画像を使用するフォールバックロジックを追加しました。

フェーズ3:パフォーマンス最適化(2025年10月)

SEOはコンテンツだけではありません - Core Web Vitals はランキングに直接影響します。PageSpeed Insightsのスコアは以下の問題に悩まされていました:

PageSpeed Insightsの優秀なデスクトップスコア:パフォーマンス99、アクセシビリティ93、ベストプラクティス96、SEO 100

最適化後のデスクトップスコア。モバイルパフォーマンスはまだ改善中です。

レンダリングブロッキングリソース

CSS @import経由で読み込まれるGoogle Fontsがレンダリングを1.6秒以上ブロックしていました。

修正: 非同期フォント読み込みに切り替えました:

<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter"
      as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter">
</noscript>

Enter fullscreen mode Exit fullscreen mode

未使用のJavaScript

幅広い互換性のためにES5をターゲットにしていたため、バンドルが不必要に肥大化していました。

修正: より良いコード分割でES2020ターゲットに更新しました:

// vite.config.ts
build: {
  target: 'es2020',
  rollupOptions: {
    output: {
      manualChunks: {
        'react-vendor': ['react', 'react-dom'],
        'router': ['react-router-dom'],
        'i18n': ['i18next', 'react-i18next'],
        'markdown': ['marked', 'prismjs']
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

キャッシュヘッダー

静的アセットが適切にキャッシュされておらず、リピーターがすべてを再ダウンロードしていました。

修正: vercel.json経由で積極的なキャッシュヘッダーを追加しました:

{
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

フェーズ4:オフページSEOとバックリンク構築(2025年10月〜11月)

オンページSEOは戦いの半分にすぎません。検索エンジンは、 外部シグナル (主に他の信頼性の高いウェブサイトからのバックリンク)に基づいてサイトの権威性も評価します。

Growth Kitによるクロスパブリッシング

10月に、ブログ記事をプラットフォーム固有のコンテンツに自動変換するClaude CodeプラグインGrowth Kitを構築しました:

  • LinkedIn - 適切なフォーマットのプロフェッショナル記事
  • Medium - 元サイトへのcanonical URLを含む長文コンテンツ
  • Dev.to - 開発者コミュニティ向けの技術コンテンツ
  • X/Twitter - フル記事へのリンク付きスレッド要約

クロスパブリッシュされた各記事には元の投稿へのcanonical URLが含まれ、以下を確保します:

  1. 重複コンテンツペナルティなし - 検索エンジンはオリジナルの場所を認識
  2. バックリンクジュースの還流 - Medium、Dev.to、LinkedInからのリンクがドメインオーソリティを向上
  3. より広いリーチ - 複数のプラットフォームでコンテンツがオーディエンスに到達
  4. ブランドの一貫性 - 各プラットフォームに最適化された同じメッセージ

ディレクトリ登録

11月に、初期バックリンクを構築するためにスタートアップおよびプロダクトディレクトリにサイトを登録しました:

  • RankingPublic - do-followリンク付きスタートアップディレクトリ
  • TinyLaunch - アーリーステージスタートアップ向けプロダクトローンチプラットフォーム
  • Product Hunt - プロダクトローンチと認知度向上のため
  • 各種AIディレクトリ - AI企業向けニッチ特化リスティング

これらのディレクトリは、検索エンジンに「これは他者が話題にしている実際のビジネスである」というシグナルを送る正当なバックリンクを提供します。

なぜバックリンクが重要か

Domain Authority(DA)とPage Authority(PA)は、サイトのランキング予測指標です。以下の要因に大きく影響されます:

  • リンク元ドメインの品質 - DA 80サイトからの1リンクは、DA 10サイトからの100リンクより価値がある
  • 関連性 - AI企業にとって、テック/AIサイトからのリンクがより重要
  • 多様性 - 多くの異なるドメインからのリンクは幅広い認知を示す
  • 自然な成長 - バックリンクの急激な増加はスパムフィルターをトリガーする可能性

私たちの戦略は、戦略的なディレクトリ登録とクロスプラットフォームパブリッシングを補完しながら、オーガニックにリンクを獲得する真に有用なコンテンツの作成に焦点を当てています。

フェーズ5:Ahrefs監査への対応(2025年12月)

トラフィックが増加するにつれて、より深いSEO分析のためにAhrefsに投資しました。サイト監査でGSCでは表示されない問題が明らかになりました:

Ahrefsサイト監査ダッシュボード:ヘルススコア100、クロール済みURL分布、クロールステータス、問題分布、エラー指標を表示

孤立ページ

いくつかのページには内部リンクがなく、クローラーにほとんど見えない状態でした。

修正: 主要なブログ記事にリンクするホームページ用のFeaturedArticlesコンポーネントを作成しました:

<section className="py-16">
  <h2>注目の記事</h2>
  <div className="grid grid-cols-3 gap-6">
    {featuredPosts.map(post => (
      <Link key={post.slug} href={`/blog/${post.slug}`}>
        <ArticleCard post={post} />
      </Link>
    ))}
  </div>
</section>

Enter fullscreen mode Exit fullscreen mode

重複メタデータ

SPAが異なるURLに対して同一のHTMLシェルを返していました。JavaScriptが最終的にユニークなコンテンツをレンダリングしますが、クローラーには重複として見えていました。

修正: VercelでUser-Agent検出を使用したクローラーターゲットのプリレンダリングを実装しました:

{
  "rewrites": [
    {
      "source": "/blog/:slug",
      "has": [
        { "type": "header", "key": "user-agent", "value": ".*bot.*" }
      ],
      "destination": "/prerendered/blog/:slug.html"
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

古いURLの301リダイレクト

URL構造を変更したとき(ブログスラッグに日付プレフィックスを追加)、古いURLが404を返し始めました。

修正: vercel.jsonに永続的なリダイレクトを追加しました:

{
  "redirects": [
    {
      "source": "/blog/old-slug",
      "destination": "/blog/2025-10-13-new-slug",
      "permanent": true
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

フェーズ6:Next.js移行(2025年12月)

すべての回避策は機能しましたが、脆弱でした。フレームワークの性質に逆らって戦っていました。

解決策は? Next.js 16 App Routerへの移行 でした。

SPA vs Next.js SSR:SPAのローディングスピナーに困惑するGooglebot vs 完全にレンダリングされたNext.js SSRコンテンツを見て喜ぶGooglebot

なぜNext.jsか?

  1. ネイティブSSR/SSG :ページはデフォルトでサーバーサイドレンダリング
  2. 組み込みメタデータAPI :手動メタタグ注入不要
  3. 自動サイトマップ生成app/sitemap.tsがそのまま動作
  4. 画像最適化 :Next/Imageがレスポンシブ画像を自動処理
  5. より良い開発者体験 :設定が少なく、構築に集中

移行

Vite ReactからNext.js 16への移行は大きな作業でした:

  • 移行PRで 166ファイルが変更
  • すべてのページをApp Router規約に変換
  • 必要に応じて'use client'を使用するようにコンポーネントを移動
  • 各ページに適切なメタデータエクスポートを実装
  • next-intlで国際化を設定

結果

移行後、SEO設定は劇的にシンプルになりました:

// app/[locale]/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getBlogPost(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime: post.publishedAt,
      authors: [post.author.name],
    },
  };
}

Enter fullscreen mode Exit fullscreen mode

プリレンダリングスクリプトは不要。クローラー検出も不要。重複コンテンツの問題もなし。

フェーズ7:最終調整(2025年12月)

Next.jsが重い作業を処理するようになったので、最終的な改良に集中しました:

ProfilePage構造化データ

チームページには、必須のmainEntityフィールドを持つ適切なProfilePageスキーマを追加しました:

{
  "@context": "https://schema.org",
  "@type": "ProfilePage",
  "mainEntity": {
    "@type": "Person",
    "name": "Shreyas Shinde",
    "jobTitle": "CEO and Founder",
    "worksFor": {
      "@type": "Organization",
      "name": "Kanaeru Labs"
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Canonical URLの一貫性

canonical URLから不要な/enプレフィックスを削除し、https://www.kanaeru.ai/en/blog/article-slugではなくhttps://www.kanaeru.ai/blog/article-slugのようなクリーンなURLを確保しました。

Open Graph画像パス

間違ったパスを指していたOG画像URLを修正し、ソーシャル共有で正しいプレビュー画像が表示されるようにしました。

学んだ教訓

1. SPAには特別な注意が必要

SPAを構築する場合、初日からSEOを計画してください。プリレンダリング、動的メタタグ、サイトマップ生成は初期アーキテクチャの一部であるべきです。

2. 適切なツールを使用する

フレームワークの性質に逆らって戦うのは疲れます。SEOが重要な場合(マーケティングサイトでは常に重要)、ネイティブSSRサポートを持つフレームワークを使用してください。

3. 複数のデータソースが不可欠

Google Search ConsoleはGoogleが見ているものを表示します。Ahrefsはクロール可能なものを表示します。PageSpeed Insightsはパフォーマンスを表示します。3つすべてが必要です。

4. 構造化データは重要

JSON-LDは単なるあった方が良いものではありません。リッチスニペットはクリックスルー率を劇的に改善でき、適切なスキーマ検証はインデックスの問題を防ぎます。

5. 内部リンクは過小評価されている

すべてのページには少なくとも1つの内部リンクが必要です。孤立ページは存在しないも同然です。

結果

これらすべての変更を実装した後:

  • ブログ記事は公開から数日以内にインデックス される
  • リッチスニペット が適切な記事マークアップで検索結果に表示される
  • Core Web Vitals がすべてのしきい値をパス
  • Ahrefsサイトヘルススコア が大幅に改善
  • オーガニックトラフィック が着実に成長

次のステップ

SEOは決して「完了」しません。私たちは継続的に:

  • GSCで新しいクロールの問題を監視
  • 月次Ahrefs監査を実施
  • ターゲットキーワードでコンテンツを最適化
  • 関連記事を通じてより多くの内部リンクを構築
  • 構造化データのカバレッジを拡大

「クロール済み - インデックス未登録」から適切な検索可視性への旅は、約2ヶ月の集中的な作業を要しました。しかし今では、今後何年も役立つ堅固な基盤ができました。


クイックリファレンス:SPA向けSEOチェックリスト

同様の課題に直面している方のために、私たちの凝縮されたチェックリストをご紹介します:

基盤

  • 動的sitemap.xml生成
  • 明示的な許可ルールを持つrobots.txt
  • すべてのページにCanonical URL
  • 多言語サイト用のhreflangタグ

構造化データ

  • ホームページにOrganizationスキーマ
  • 記事にBlogPostingスキーマ
  • チームページにProfilePageスキーマ
  • GoogleのRich Results Testで検証

パフォーマンス

  • 非同期フォント読み込み
  • コード分割と遅延読み込み
  • 画像最適化
  • 静的アセットのキャッシュヘッダー

コンテンツアクセシビリティ

  • クローラー向けに重要なページをプリレンダリング
  • URL変更時の301リダイレクト
  • 内部リンク戦略
  • 孤立ページなし

監視

  • Google Search Console
  • Ahrefsまたは類似のSEOツール
  • PageSpeed Insights
  • 定期的な監査

SPA SEOや移行プロセスについてご質問がありますか?無料相談を予約してください。


Originally published at Kanaeru AI

Top comments (0)