本文へ移動
hanzochang

技術と実践のノート

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

公開

著者:株式会社hanzochang

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

はじめに

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アプリに以下の設定変更が適用されました:

text
ios_in_app_article_webview_disable_javascript_during_prefetch: true
text
ios_in_app_article_webview_disable_javascript_during_prefetch: true

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

参考記事:

Prefetchの技術的な特徴

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

特徴詳細
トリガーリンクカードが画面に表示された時点
取得内容HTMLのみ
JavaScript無効化された状態で取得
実装方式WKWebView(iOSのWebブラウザエンジン)
OGPメタタグサーバー側で生成されたものは正常に取得可能
特徴詳細
トリガーリンクカードが画面に表示された時点
取得内容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のPV500500
サーバー負荷通常20倍
指標従来の仕様新しい仕様(Prefetch有効)
タイムライン表示回数10,000回10,000回
サーバーへのHTMLリクエスト500回(クリック数)10,000回(表示数すべて)
実際のクリック数500回500回
Google AnalyticsのPV500500
サーバー負荷通常20倍

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

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

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

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

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

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

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

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

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

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

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

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

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

  return <div>{post?.content}</div>;
}
typescript
// クライアントサイドでデータ取得する場合
'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)では、ビルド時に全ページを生成します。

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

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

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

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

実装方法(Next.js App Router)

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

typescript
// 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>
  );
}
typescript
// 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で空配列を返す
typescript
export async function generateStaticParams() {
  return [] // 事前ビルドなし
}
typescript
export async function generateStaticParams() {
  return [] // 事前ビルドなし
}

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

2. revalidateでキャッシュ期間を設定
typescript
export const revalidate = 3600 // 1時間(秒単位)
typescript
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(古いキャッシュを返す)
アクセス段階処理内容サーバー負荷レスポンス元
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配信)
指標Lazy ISR導入前(SSR)Lazy ISR導入後
バズ時のサーバー負荷タイムライン表示数分のHTMLレンダリング初回のみ
CDNヒット率-90%以上
サーバーコスト高い大幅に削減
ページ表示速度普通高速(CDN配信)
その他の対策・注意点

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

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

typescript
'use client';

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

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

  return <div>{post?.content}</div>;
}
typescript
'use client';

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

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

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

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

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

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

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

  return (
    <div>
      <PostContent post={post} />
      {/* ユーザー固有部分はクライアントで取得 */}
      <UserSpecificContent />
    </div>
  );
}
typescript
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への切り替えを検討してみてください。

参考資料

この記事が参考になったら

この記事が役に立ったと思いましたら、よろしければフォローをお願いします。

Xでフォローする

Webサイトのパフォーマンス改善やサーバー負荷対策を検討されていましたら、お気軽にご連絡ください。

お問い合わせ

この記事を書いた人

Webプランナー、開発会社の創業メンバーを経て、Fintech・DX・Web3の企画・開発に従事。2021年に独立し、2023年に株式会社hanzochangを設立。現在はAIを活用した業務改善と、自社サービス「指定管理者制度AI」の開発・運用に取り組んでいます。

半澤勇大のXアカウント(新しいタブで開く)

この記事の株式会社hanzochangについて
少し紹介させてください!

この記事に関連する当社のサービス

業務改善・AI活用支援

転記・集計・資料作成にかかる時間を減らしませんか?

現在の業務と使っているシステムを整理し、AIが役立つ作業から改善します。試作で効果を確かめ、現場で使うアプリの開発・運用まで支援します。

サービスの詳細を見る
Jくんが多様な業務アプリを開発するイラスト。背景に多数のアプリが並ぶ。
  • 業務の整理

    手間のかかる作業と、改善する順番を明確に。

  • 試作・アプリ開発

    実際の手順に合わせて、使える仕組みを開発。

  • 導入・運用支援

    使い方の共有から、導入後の改善まで対応。

業務改善・AI活用について、お気軽にご相談ください。