hanzochang
hanzochang
はじめに
対象読者
Xアプリの新しいPrefetch仕様とは
2025年11月の仕様変更
仕様変更の情報源
Prefetchの技術的な特徴
なぜこの仕様にしたのか
どんな時に問題になるのか:バズが発生した時
バズ時のPrefetchの影響
具体例:投稿が1万回表示された場合
特に影響を受けやすいサイト
SSRサイトで発生する具体的な問題
Next.jsでSSRを使っている場合の例
CSR(Client-Side Rendering)の場合は影響を受けない
対策:Next.jsの場合はLazy ISRを導入
筆者の事例:100万件近いデータでの対策
Lazy ISRとは
100万件のページで事前ビルドしない理由
実装方法(Next.js App Router)
設定の2つのポイント
1. `generateStaticParams`で空配列を返す
2. `revalidate`でキャッシュ期間を設定
XのPrefetchに対するLazy ISRの効果
導入前後の比較
その他の対策・注意点
CSRに切り替える選択肢もある
ユーザー固有コンテンツの扱い
まとめ
XのiOSアプリPrefetch仕様のポイント
問題が発生するサイト
Next.jsでの対策事例
参考資料
https://s3.ap-northeast-1.amazonaws.com/hanzochang.com/_v2/1767067083995_xrg1czphul.png

XのiOSアプリが外部サイトをPrefetch(先読み)する仕様とその影響

2025年11月、XのiOSアプリが外部リンクを事前読み込みする仕様に変更。バズ発生時にサーバー負荷だけ増えてPVがカウントされない問題とその対策を解説します。

公開日2025.12.30

更新日2025.12.30

はじめに

2025年11月、Xアプリ(旧Twitter iOS版)が外部リンクを事前読み込み(Prefetch)する仕様に変更されました。

この仕様変更により、バズが発生した際に「サーバー負荷だけ増えてPVとしてカウントされない」という問題が発生するケースがあります。この記事では、Xの新仕様の詳細と、実際に発生する問題、そして対策について解説します。

対象読者

  • Xで投稿がバズることがあるサイト運営者
  • サーバー負荷の原因を調査している開発者
  • SSR(Server-Side Rendering)を使用しているWebサイト運営者

Xアプリの新しいPrefetch仕様とは

2025年11月の仕様変更

Xアプリ(iOS版)は2025年11月頃から、タイムライン上の外部リンクに対して プリフェッチ(Prefetch / 先読み) を実装しました。

従来の挙動:

  • ユーザーがリンクをタップ → コンテンツを読み込み開始

新しい仕様:

  • リンクが画面に表示された時点 → バックグラウンドでコンテンツの読み込みを開始
  • ユーザーがタップする前から読み込みが発生

仕様変更の情報源

この仕様変更は、複数の技術メディアや開発者コミュニティで報告されています。

PPC Land(2025年11月8日)

"X iOS update disables JavaScript during link prefetch, generating artificial traffic"

2025年11月5日午後4時33分(現地時間)、XのiOSアプリに以下の設定変更が適用されました:

ios_in_app_article_webview_disable_javascript_during_prefetch: true

この設定により、XアプリはリンクのPrefetch(事前読み込み)時にJavaScriptを無効化するようになりました。実際のクリック時には通常通りJavaScriptが有効化されます。

参考記事:

Prefetchの技術的な特徴

Xアプリのプリフェッチは以下の技術的特性を持っています。

特徴詳細
トリガー
リンクカードが画面に表示された時点
取得内容
HTMLのみ
JavaScript
無効化された状態で取得
実装方式
WKWebView(iOSのWebブラウザエンジン)
OGPメタタグ
サーバー側で生成されたものは正常に取得可能

なぜこの仕様にしたのか

Xアプリがこの仕様を採用した理由として考えられるのは:

  1. UX向上:ユーザーがタップした瞬間に即座にページを表示できる
  2. 広告タグの誤発火防止:JSを無効化することで、プリフェッチ段階で広告が課金されることを防ぐ
  3. リンクプレビューの充実:OGPメタタグを事前に取得し、タイムラインでのプレビュー表示を向上

ユーザー体験の向上を目的とした仕様変更ですが、Webサイト側には予期しない影響が発生するケースがあります。

どんな時に問題になるのか:バズが発生した時

バズ時のPrefetchの影響

Xで投稿がバズると、以下のような流れでアクセスが発生します。

  1. 投稿が拡散される → タイムラインに表示される回数が急増
  2. 表示されただけでPrefetchが発火 → 数千〜数万のHTMLリクエストがサーバーに届く
  3. 実際にリンクをクリックする人 → その一部のみ

つまり、「表示された数」と「クリックされた数」の差が大きいほど、無駄なサーバー負荷が発生します。

具体例:投稿が1万回表示された場合

指標従来の仕様新しい仕様(Prefetch有効)
タイムライン表示回数
10,000回
10,000回
サーバーへのHTMLリクエスト
500回(クリック数)
10,000回(表示数すべて)
実際のクリック数
500回
500回
Google AnalyticsのPV
500
500
サーバー負荷
通常
20倍

バズが発生すると、サーバー負荷だけが急増し、PV数やコンバージョンは比例して増えないという現象が起きます。

特に影響を受けやすいサイト

以下のようなサイトは、この問題の影響を受けやすい傾向にあります。

  • SSR(Server-Side Rendering)を使用しているサイト:リクエストごとにサーバーでHTMLを生成
  • DBアクセスが必要なページ:記事の本文や関連記事をDBから取得している
  • 計算処理が重いページ:ランキング計算、レコメンデーション生成など
  • バズりやすいコンテンツを扱うサイト:ニュースサイト、バイラルメディアなど

SSRサイトで発生する具体的な問題

Next.jsでSSRを使っている場合の例

Next.jsのSSR(Server-Side Rendering)を使用している場合、以下のような問題が発生します。

// app/posts/[id]/page.tsx
export default async function PostPage({ params }: { params: { id: string } }) {
  // Prefetch時にもこの処理が実行される
  const post = await fetchPostFromDB(params.id);
  const relatedPosts = await fetchRelatedPosts(params.id);

  return (
    <div>
      <h1>{post.title}</h1>
      <PostContent content={post.content} />
      {/* Google Analyticsなどのタグ(JSが無効なため発火しない) */}
      <Analytics />
    </div>
  );
}

この構成では、以下の問題が発生します。

処理Prefetch時の動作実際のクリック時の動作問題点
サーバー側のHTML生成
✅ 実行される
✅ 実行される
Prefetchでもサーバー負荷が発生
DBアクセス
✅ 実行される
✅ 実行される
無駄なDB負荷
計算処理
✅ 実行される
✅ 実行される
無駄なCPU使用
Google Analytics
❌ 発火しない(JS無効)
✅ 発火する
PVとして計測されない
その他の計測タグ
❌ 発火しない
✅ 発火する
アクセス解析に反映されない

結果として、サーバー負荷だけ消費してPVとしてカウントされないという非効率な状態になります。

CSR(Client-Side Rendering)の場合は影響を受けない

一方、CSR(クライアントサイドレンダリング)を使っているサイトは、この問題の影響を受けにくい傾向にあります。

// クライアントサイドでデータ取得する場合
'use client';

export default function PostPage() {
  const [post, setPost] = useState(null);

  useEffect(() => {
    // JSが無効なPrefetch時は実行されない
    fetchPost().then(setPost);
  }, []);

  return <div>{post?.content}</div>;
}

XのPrefetchはJSを無効化しているため、useEffect内の処理は実行されません。サーバー負荷は発生しませんが、OGPメタタグが生成されないというデメリットもあります。

対策:Next.jsの場合はLazy ISRを導入

筆者の事例:100万件近いデータでの対策

筆者が運営しているサイトでは、以下の状況でした。

  • ページ数:約100万件(ユーザー投稿ベース)
  • 構成:Next.js App RouterのSSR
  • 課題:Xでバズった際にサーバー負荷が急増

この状況で、「事前にすべてのページをビルド(Static Site Generation)」することは現実的ではありませんでした。

そこで採用したのが Lazy ISR(Incremental Static Regeneration) という手法です。

Lazy ISRとは

Lazy ISRは、Next.jsのISR機能を活用した戦略で、以下の特徴を持ちます。

  • ビルド時には静的ページを生成しないgenerateStaticParamsで空配列を返す)
  • 初回アクセス時にページを静的生成してキャッシュ
  • 一度生成されたページはCDNから配信される

この戦略により、XのPrefetchが 「キャッシュ生成のトリガー」 として機能します。

100万件のページで事前ビルドしない理由

従来のSSG(Static Site Generation)では、ビルド時に全ページを生成します。

// 従来のSSG(100万件では非現実的)
export async function generateStaticParams() {
  // 全ページのIDを取得してビルド時に生成
  const allPostIds = await fetchAllPostIds() // 100万件!
  return allPostIds.map((id) => ({ id }))
}

100万件のページを事前ビルドすると:

  • ビルド時間が数時間〜数十時間かかる
  • ストレージ容量が膨大になる
  • デプロイのたびに全再ビルドが必要

そのため、 アクセスがあったページだけを静的化する「Lazy ISR」 を採用しました。

実装方法(Next.js App Router)

筆者が実際に設定したコードは以下の通りです。

// app/posts/[id]/page.tsx

// 事前ビルドは行わない(空配列を返す)
export async function generateStaticParams() {
  return []; // 100万件を事前ビルドしない
}

// ISRを有効化(1時間キャッシュ)
export const revalidate = 3600;

export default async function PostPage({ params }: { params: { id: string } }) {
  // 初回アクセス時のみサーバー処理が実行される
  const post = await fetchPostFromDB(params.id);
  const relatedPosts = await fetchRelatedPosts(params.id);

  return (
    <div>
      <h1>{post.title}</h1>
      <PostContent content={post.content} />
      <Analytics />
    </div>
  );
}

設定の2つのポイント

1. generateStaticParamsで空配列を返す

export async function generateStaticParams() {
  return [] // 事前ビルドなし
}

事前ビルドをスキップし、アクセスがあったページだけを静的化します。

2. revalidateでキャッシュ期間を設定

export const revalidate = 3600 // 1時間(秒単位)

revalidateを設定することでISRが有効になり、指定した期間キャッシュが保持されます。

コンテンツの更新頻度に応じて設定を調整してください:

  • 3600(1時間):頻繁に更新されるコンテンツ
  • 86400(24時間):あまり更新されないコンテンツ

XのPrefetchに対するLazy ISRの効果

Lazy ISRを導入すると、XのPrefetchは以下のように作用します。

アクセス段階処理内容サーバー負荷レスポンス元
1. XのPrefetch(初回)
サーバーでHTML生成 → CDNにキャッシュ保存
✅ あり
サーバー
2. ユーザーの実際のクリック
キャッシュされたHTMLを配信
❌ なし
CDN
3. 別のユーザーのアクセス
キャッシュから配信
❌ なし
CDN
4. revalidate期間経過後
バックグラウンドで再生成
✅ あり(1回のみ)
CDN(古いキャッシュを返す)

重要なポイント

  • XのPrefetchが「初回アクセス = キャッシュ生成のトリガー」として機能
  • 実際のユーザーアクセスはすでにキャッシュ済みのため、サーバー負荷ゼロで高速配信
  • バズ時も、1回目のPrefetchでキャッシュが作られれば、それ以降はCDNから配信される

導入前後の比較

筆者のサイトでは、Lazy ISR導入前後で以下のような変化がありました。

指標Lazy ISR導入前(SSR)Lazy ISR導入後
バズ時のサーバー負荷
タイムライン表示数分のHTMLレンダリング
初回のみ
CDNヒット率
-
90%以上
サーバーコスト
高い
大幅に削減
ページ表示速度
普通
高速(CDN配信)

その他の対策・注意点

CSRに切り替える選択肢もある

もしOGPメタタグが不要な場合は、CSR(Client-Side Rendering)に切り替えることでPrefetchの影響を完全に回避できます。

'use client';

export default function PostPage() {
  const [post, setPost] = useState(null);

  useEffect(() => {
    // JSが無効なPrefetch時は実行されない
    fetchPost().then(setPost);
  }, []);

  return <div>{post?.content}</div>;
}

ただし、X上でのリンクプレビュー(OGP画像・タイトル)が表示されなくなるデメリットがあります。

ユーザー固有コンテンツの扱い

キャッシュと相性が悪いユーザーごとのコンテンツ(ログイン状態など)は、クライアントサイドで取得する必要があります。

export default async function Page() {
  // 全ユーザー共通部分(キャッシュ可能)
  const post = await fetchPost();

  return (
    <div>
      <PostContent post={post} />
      {/* ユーザー固有部分はクライアントで取得 */}
      <UserSpecificContent />
    </div>
  );
}

まとめ

XのiOSアプリPrefetch仕様のポイント

  • 2025年11月から実装:リンクが画面に表示された時点でHTMLを事前読み込み
  • JavaScript無効化:計測タグが発火しないため、PVとしてカウントされない
  • バズ時に影響大:タイムライン表示数分のHTMLリクエストが発生

問題が発生するサイト

  • SSR(Server-Side Rendering)を使用しているサイト
  • DBアクセスや計算処理が必要なページ
  • バズることがあるコンテンツを扱うサイト

Next.jsでの対策事例

筆者の事例(約100万ページのサイト)では、Lazy ISRを導入することで:

  • ✅ 事前ビルド不要で大規模サイトにも適用可能
  • ✅ XのPrefetchをキャッシュ生成トリガーとして活用
  • ✅ バズ時も初回のみサーバー負荷、以降はCDN配信
  • ✅ サーバーコストを大幅に削減

XアプリのPrefetch仕様は、適切に対応すれば逆にパフォーマンス最適化の機会になります。自サイトの構成に応じて、Lazy ISRやCSRへの切り替えを検討してみてください。

参考資料

picture
hanzochang - 半澤勇大
慶應義塾大学卒業後、Webプランナーとして勤務。 ナショナルクライアントのキャンペーンサイトの企画・演出を担当。 その後開発会社に創業メンバーとして参加。 Fintech案件や大手企業のDXプロジェクトに関わり、その後個人事業主として独立し、 2023年にWeb3に特化した開発会社として法人化しました。 現在はWeb3アプリ開発を中心にAI開発フローの整備を行っています。
また、趣味で2017年ごろより匿名アカウントでCryptoの調査等を行い、 ブロックチェーンメディアやSNSでビットコイン論文等の図解等を発信していました。
X (Twitter)

お問い合わせはこちらから

ご希望に応じて職務経歴書や過去のポートフォリオを提出可能ですので、必要な方はお申し付けください。
また内容とによっては返信ができない場合や、お時間をいただく場合がございます。あらかじめご了承ください。