サムネイル

react-view-transitions解説 — Next.jsでライブラリ不要のページ遷移を実装する

  • 0

はじめまして、もるふぉです。

エンジニアをやりながら、今はほぼコードを書かない開発スタイルに移行しました。

「書けないから書かない」じゃなくて、「書けるから書かなくていい」という話です。

実案件ベースで気づいたことだけ書いています。

よければXもフォローしてもらえると嬉しいです → X(@morphox_ai)

ページ遷移アニメーション、ずっと後回しにしていませんでしたか?

「framer-motionの AnimatePresence 、ルーティングとの統合、exitプロパティ...」と考えただけで気が重くなって、「まあ、動けばいいか」でリリースしてしまった経験、ありますよね。

2026年4月3日、Next.js公式がコーディングエージェント向けスキル「react-view-transitions」を発表しました。

この発表をきっかけに、ReactのViewTransition機能を改めて整理してみます。

Next.js 15.2以降では experimental.viewTransition フラグひとつで有効になり、framer-motionを使わずにスムーズなページ遷移アニメーションが実装できます。

framer-motionの設定ファイルと格闘していた日々、もう終わりにしませんか。

React ViewTransitionとは何か

記事の画像

View Transitions APIの概要

View Transitions APIは、ブラウザがネイティブで提供するアニメーション機構です。

ページの状態が変わるとき、ブラウザが「変更前のスナップショット」と「変更後のライブ表現」を自動的に作成し、その間をスムーズにアニメーションしてくれます。

「それ、JavaScriptで全部やってたやつですよね?」という話です。

従来はJavaScriptで複雑なアニメーションライブラリを組み込む必要がありましたが、View Transitions APIではCSSの疑似要素(::view-transition-old::view-transition-new)でアニメーションを定義するだけで動きます。

しかもブラウザレベルでハードウェアアクセラレーションされているので、JSで頑張って書いたアニメーションより、むしろ滑らかになることすらあります。

React / Next.jsネイティブ統合のViewTransitionが新しい理由

ここが面白いところなんです。

ReactのCanaryチャンネルでは コンポーネントが利用できます。

これまでのView Transitions APIは document.startViewTransition() を手動で呼び出す必要があり、Reactの宣言的なパラダイムとは相性がよくなかったんですよね。

「イベントハンドラーの中でAPIを呼んで、その中でstateを更新して...」という手続き的なコードを書かざるを得なかった。

はこれを解決します。

コンポーネントで子要素をラップするだけで、startTransition と連動してアニメーションが自動発火します。

しかも enterexitupdateshare といったpropsで、どのタイミングのアニメーションを適用するか宣言的に指定できます。

Next.jsでは next.config.ts に1行追加するだけで、ルーティング遷移時に自動的にViewTransitionが有効になります。

つまり、既存コードをほぼ触らずにアニメーションが動き出す、ということです。

next-view-transitions(旧サードパーティ)とReactネイティブViewTransitionの違い

「あ、それ next-view-transitions でやってたやつですよね?」という方もいると思います。

これまでNext.jsでView Transitionsを使うには、Shu Ding氏が作った next-view-transitions というサードパーティパッケージが定番でした。

このパッケージは でアプリをラップし、専用の コンポーネントを使う設計でした。

対して、Reactネイティブの を活用する現在のアプローチでは、追加のProviderやカスタムLinkは不要です。

Next.jsの next/link がそのまま使えるので、既存コードへの影響が最小限で済みます。

ここが一番大きな違いですね。

既存プロジェクトへの導入コストが、サードパーティ版と比べて格段に下がっています。

では実際に、どう動かすのかを見てみましょう。

React ViewTransitionのセットアップ — next.config.tsの1行だけ

Next.jsの動作環境と必要バージョン

ViewTransitionを使うために必要な環境は以下のとおりです。

要件
バージョン
React
canaryチャンネル(安定版19系には未含有)
Next.js
15.2以降(experimentalフラグ)
Node.js
18.18以降

は2026年4月時点で react@canary / react@experimental チャンネルで利用可能です。

安定版のReact 19には含まれていないため、Next.js 15.2以降の experimental.viewTransition フラグを通じて利用するのが現実的な選択肢です。

なお、このフラグは実験的機能であり、今後変更される可能性があります。

next.config.tsにexperimental.viewTransitionを追加する

設定を見てください。

本当にこれだけです。

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  experimental: {
    viewTransition: true,
  },
};

export default nextConfig;

「え、これだけ?」って思いましたよね。

これだけで、Next.jsのルーター遷移時にView Transitions APIが自動的に有効になります。

experimental.viewTransition を有効にすると、Next.jsがナビゲーション時に自動で document.startViewTransition() を呼び出してくれるので、開発者は コンポーネントの配置に集中できます。

framer-motionでAnimate Presenceを設定していた時間を思い出すと、なかなか感慨深いです。

ブラウザ対応状況(Chrome / Safari / Firefox)

「でも、ブラウザ対応が不安で...」という気持ち、わかります。

2026年4月時点のブラウザ対応状況をまとめます。

ブラウザ
同一ドキュメント遷移
クロスドキュメント遷移
Chrome 111+
対応
126+で対応
Edge 111+
対応
126+で対応
Safari 18+
対応
18.2+で対応
Firefox 144+
対応
未対応

重要なのは、View Transitions APIはグレースフルデグラデーションする設計だという点です。

非対応ブラウザではアニメーションがスキップされるだけで、DOM更新自体は正常に動作します。

つまり、非対応ブラウザで見ても「壊れる」のではなく「アニメーションなしで普通に動く」だけ。

リスクゼロで今すぐ導入できるんですよ。

これ、結構大事なポイントです。

ブラウザ対応を盾にして後回しにできない理由がなくなりました。

ではいよいよ、コンポーネントの実装を見てみましょう。

基本 — Next.jsでのViewTransitionナビゲーション間アニメーション

ViewTransitionコンポーネントの基本的な使い方

の使い方はとてもシンプルです。

アニメーションさせたい要素をラップするだけ。

import { ViewTransition } from 'react';

function MyComponent() {
  return (
    <ViewTransition enter="auto" exit="auto">
      <div className="card">
        <h2>コンテンツ</h2>
        <p>この要素が表示・非表示時にアニメーションします</p>
      </div>
    </ViewTransition>
  );
}

ポイントは、状態変更を startTransition で包む必要があることです。

import { useState, startTransition } from 'react';

function App() {
  const [show, setShow] = useState(false);

  return (
    <>
      <button
        onClick={() => {
          startTransition(() => {
            setShow(prev => !prev);
          });
        }}
      >
        {show ? '非表示' : '表示'}
      </button>
      {show && <MyComponent />}
    </>
  );
}

startTransition で状態更新をラップしないとViewTransitionは発火しません。

「動かない!」と5分悩む前に、ここを確認してください。

最初にハマりやすいポイントなので、覚えておいてください。

enter / exit / update / shareの各propの使い分け

には4つのアニメーションタイプを指定するpropsがあります。

prop
トリガー
用途
enter
要素がTransition内で挿入されたとき
フェードイン、スライドイン
exit
要素がTransition内で削除されたとき
フェードアウト、スライドアウト
update
子要素のDOMに変更があったとき
クロスフェード
share
同じnameのViewTransitionがunmount/mountされたとき
共有要素の移動

料理に例えると、「入店・退店・席移動・テーブル間の移動」それぞれに違う演出を当てるイメージです。

それぞれに "auto"(デフォルトアニメーション)、"none"(無効化)、またはCSSクラス名の文字列を指定できます。

<ViewTransition
  enter="slide-in"
  exit="slide-out"
  update="none"
  default="auto"
>
  <Card />
</ViewTransition>

default propを指定すると、個別に設定していないタイプすべてにそのアニメーションが適用されます。

全部設定しなくていい、というのが地味に助かります。

React ViewTransitionのEnter/Exit実装コード全体

Enter/Exitアニメーションの実用的な例を示します。

// components/AnimatedItem.tsx
import { ViewTransition } from 'react';

export function AnimatedItem({ children }: { children: React.ReactNode }) {
  return (
    <ViewTransition enter="fade-slide-in" exit="fade-slide-out" default="none">
      {children}
    </ViewTransition>
  );
}
/* styles/transitions.css */
::view-transition-new(.fade-slide-in) {
  animation: fadeSlideIn 300ms ease-out;
}

::view-transition-old(.fade-slide-out) {
  animation: fadeSlideOut 300ms ease-in;
}

@keyframes fadeSlideIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fadeSlideOut {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-20px);
  }
}

CSSを書くだけでアニメーションが動くのは、framer-motionのJSベースのアニメーション定義に比べると本当にラクですね。

「JavaScriptでアニメーションを管理する」という考え方から解放される感覚があります。

ここまでで基本的な使い方はわかりました。

次が、ViewTransitionの中でも特に「お、これはすごい」と思った機能です。

共有要素トランジション — View Transitions APIでリストから詳細へスムーズに遷移

記事の画像

name propで要素を紐付ける

正直、これを初めて動かしたとき、鳥肌が立ちました。

共有要素トランジションは、ViewTransitionの目玉機能です。

一覧ページのサムネイルと詳細ページのヒーロー画像が、同じ要素として滑らかに移動するあの演出です。

name propに同じ文字列を指定するだけで実現できます。

// 一覧ページ側
<ViewTransition name="product-image">
  <img src={product.thumbnail} alt={product.name} />
</ViewTransition>

// 詳細ページ側
<ViewTransition name="product-image">
  <img src={product.heroImage} alt={product.name} />
</ViewTransition>

ブラウザが自動的に2つの要素間をアニメーションしてくれます。

想像してみてください。

ECサイトで商品一覧から商品詳細に遷移したとき、サムネイルが滑らかに拡大しながらヒーロー画像になる。

これが name propひとつで実現できるんですよ。

Next.js App Routerでの一覧ページと詳細ページの実装例

Next.js App Routerでの実装例を見てみましょう。

// app/products/page.tsx
import Link from 'next/link';
import { ViewTransition } from 'react';

export default function ProductList() {
  return (
    <div className="grid">
      {products.map((product) => (
        <Link key={product.id} href={`/products/${product.id}`}>
          <ViewTransition name={`product-${product.id}`}>
            <div className="product-card">
              <img src={product.image} alt={product.name} />
              <h3>{product.name}</h3>
            </div>
          </ViewTransition>
        </Link>
      ))}
    </div>
  );
}
// app/products/[id]/page.tsx
import { ViewTransition } from 'react';

export default function ProductDetail({ params }: { params: { id: string } }) {
  const product = getProduct(params.id);

  return (
    <div className="detail">
      <ViewTransition name={`product-${product.id}`}>
        <img
          src={product.image}
          alt={product.name}
          className="hero-image"
        />
      </ViewTransition>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

next/link でのナビゲーション時にViewTransitionが発火する点がポイントです。

router.push() でも動作しますが、next/link を使うのが最もシンプルです。

view-transition-nameの重複を避けるパターン

一点だけ、落とし穴があります。

name propの値はアプリ全体で一意でなければなりません。

同じ name を持つ が同時にマウントされるとエラーになります。

一覧ページでは複数のカードが同時に表示されるため、動的なIDを使って一意性を担保してください。

// OK: 動的IDで一意にする
<ViewTransition name={`product-${product.id}`}>

// NG: 固定名だと一覧ページで重複する
<ViewTransition name="product-image">

最初に見落としがちなポイントなので、気をつけてくださいね。

さらにここからが、実際のアプリ開発で「やっぱり必要だよな」と感じる場面の話です。

ページスライド — Next.jsでforward / backwardの方向別ViewTransitionアニメーション

記事の画像

Transition Typesで方向を指定する

ページを「進む/戻る」で動きの方向を変えたい、と思ったことはありませんか?

ネイティブアプリのUIと同じ、あの感覚です。

addTransitionType と組み合わせてTransition Typesを使うと、これが実現できます。

import { ViewTransition, addTransitionType } from 'react';
import { startTransition } from 'react';

function Navigation() {
  const handleForward = () => {
    startTransition(() => {
      addTransitionType('slide-forward');
      router.push('/next-page');
    });
  };

  const handleBack = () => {
    startTransition(() => {
      addTransitionType('slide-backward');
      router.back();
    });
  };

  return (
    <>
      <button onClick={handleBack}>戻る</button>
      <button onClick={handleForward}>進む</button>
    </>
  );
}

レイアウトファイルで、Transition Typeに応じたアニメーションを指定します。

// app/layout.tsx
import { ViewTransition } from 'react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <ViewTransition
          default={{
            'slide-forward': 'slide-left',
            'slide-backward': 'slide-right',
            default: 'auto',
          }}
        >
          {children}
        </ViewTransition>
      </body>
    </html>
  );
}

ViewTransitionのCSSアニメーション定義

方向別のスライドアニメーションをCSSで定義します。

/* 前方向(左にスライド) */
::view-transition-old(.slide-left) {
  animation: slideOutLeft 400ms ease-in-out;
}

::view-transition-new(.slide-left) {
  animation: slideInRight 400ms ease-in-out;
}

/* 後方向(右にスライド) */
::view-transition-old(.slide-right) {
  animation: slideOutRight 400ms ease-in-out;
}

::view-transition-new(.slide-right) {
  animation: slideInLeft 400ms ease-in-out;
}

@keyframes slideOutLeft {
  to { transform: translateX(-100%); }
}

@keyframes slideInRight {
  from { transform: translateX(100%); }
}

@keyframes slideOutRight {
  to { transform: translateX(100%); }
}

@keyframes slideInLeft {
  from { transform: translateX(-100%); }
}

ネイティブアプリのような「進む/戻る」のスライドが、CSSだけで実現できるのは本当に気持ちいいですね。

Next.jsレイアウトとViewTransitionを組み合わせた実装コード全体

レイアウトとCSSを組み合わせた完全な実装例です。

// app/layout.tsx
import { ViewTransition } from 'react';
import './transitions.css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja">
      <body>
        <nav>
          <NavigationButtons />
        </nav>
        <ViewTransition
          default={{
            'slide-forward': 'slide-left',
            'slide-backward': 'slide-right',
            default: 'auto',
          }}
        >
          <main>{children}</main>
        </ViewTransition>
      </body>
    </html>
  );
}

これだけで、ページ全体がスライドアニメーション付きで遷移します。

framer-motionでこれをやろうとすると、AnimatePresencemotion.div、exitプロパティの設定、ルーティングとの統合...と、かなりの手間がかかっていたはずです。

その差を実感したくなりませんか?

次は、ローディング中のUXを改善する話です。

スムーズなローディング遷移 — ViewTransitionとSuspenseの組み合わせ

ViewTransitionとSuspenseを組み合わせた場合の動作

「スケルトンUIからコンテンツが切り替わるときの、あの唐突な感じ」が気になっていた方、ここを見てください。

を組み合わせると、ローディング状態から実コンテンツへの遷移もアニメーションできます。

import { Suspense } from 'react';
import { ViewTransition } from 'react';

function Page() {
  return (
    <Suspense
      fallback={
        <ViewTransition>
          <LoadingSkeleton />
        </ViewTransition>
      }
    >
      <ViewTransition>
        <ActualContent />
      </ViewTransition>
    </Suspense>
  );
}

この場合、LoadingSkeleton から ActualContent への切り替え時に、ブラウザがクロスフェードアニメーションを自動適用します。

フォールバック表示とフェードイン

もう一つのパターンとして、 を包む方法もあります。

<ViewTransition>
  <Suspense fallback={<Skeleton />}>
    <AsyncComponent />
  </Suspense>
</ViewTransition>

この書き方だと、フォールバックから実コンテンツへの切り替えが update アニメーションとして扱われます。

用途に応じて使い分けてください。

スケルトンUIからコンテンツへの遷移がなめらかになるだけで、体感的な速度が全然違います。

「実際の速度は変わってないのに速く感じる」という、UXの魔法ですね。

ユーザー体験に直結する部分なので、ぜひ試してみてほしいですね。

コンポジション — 複数のViewTransitionを組み合わせる応用パターン

ここからは応用的なテクニックです。

基本の使い方を押さえた上で、実務でよく使う設計パターンを紹介します。

ネストしたViewTransitionの優先度ルール

はネストできます。

内側のViewTransitionが外側よりも優先されるルールです。

<ViewTransition default="auto">
  <header>ヘッダー(外側のautoが適用)</header>
  <ViewTransition enter="slide-in" exit="slide-out">
    <main>メインコンテンツ(内側の設定が優先)</main>
  </ViewTransition>
  <footer>フッター(外側のautoが適用)</footer>
</ViewTransition>

CSSのカスケードに近い考え方で直感的ですよね。

この仕組みにより、ページ全体にはデフォルトのフェードを適用しつつ、メインコンテンツだけスライドアニメーションにする、といった細かい制御が可能です。

実用的な設計パターン

実務では以下のような階層設計が使いやすいです。

// app/layout.tsx - グローバルレイアウト
<ViewTransition default="auto">
  <Header />
  <ViewTransition
    default={{
      'slide-forward': 'slide-left',
      'slide-backward': 'slide-right',
      default: 'fade',
    }}
  >
    {children}
  </ViewTransition>
  <Footer />
</ViewTransition>

ヘッダーとフッターはデフォルトのクロスフェード、ページ本体は方向に応じたスライド、という構成です。

コンポジションのおかげで「全体のルール」と「個別のルール」を自然に分離できます。

Reactのコンポーネント設計とも非常に相性がいいですね。

本番に出す前に、あとひとつだけ確認してほしいことがあります。

アクセシビリティ対応は必須

prefers-reduced-motionの実装方法

アニメーションは見た目の改善になりますが、前庭障害のあるユーザーにとっては体調悪化の原因になることがあります。

「アニメーションを減らす設定」をOSで有効にしているユーザーが、予期しないアニメーションで不調を感じる、というのは本当に避けなければいけない事態です。

prefers-reduced-motion メディアクエリで、ユーザーの設定を尊重するのは必須です。

CSSで一括無効化するスニペット

以下のCSSをグローバルスタイルに追加してください。

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
    transition: none !important;
  }
}

たった4行です。

これがあるかないかで製品の品質が変わります。

本番に出すコードには必ず入れてくださいね。

まとめ — react-view-transitionsで何が変わるか

ViewTransitionがもたらす変化をまとめます。

Before
After
framer-motion等の外部ライブラリが必須
React標準のだけで完結
JSベースのアニメーション定義
CSSの疑似要素で宣言的に定義
ProviderやカスタムLinkが必要
next/linkがそのまま使える
バンドルサイズ増加
ゼロ依存、ブラウザネイティブ
複雑な設定ファイル
next.config.tsの1行で有効化

「ページ遷移アニメーションは面倒だから後回し」にしていたプロジェクトでも、next.config.ts に1行足すだけで始められます。

コード量は圧倒的に少なく、パフォーマンスはブラウザネイティブで最適化済み。

しかもグレースフルデグラデーションするので、非対応ブラウザでも壊れない。

個人的には、framer-motionを入れる理由がどんどん減っていると感じています。

もちろんframer-motionにはレイアウトアニメーションやジェスチャー対応など固有の強みもありますが、ページ遷移に限って言えば、View Transitions APIの方がシンプルで軽量です。

まずこれだけやってみてください。

  1. next.config.tsexperimental: { viewTransition: true } を追加
  2. 動かしたい要素を でラップ
  3. ブラウザで確認

この3ステップ、5分かかりません。

アプリの印象がガラッと変わる体験を、ぜひ手を動かして確認してみてください。

AIコーディングツールの最新動向に興味がある方は、こちらの記事もどうぞ。

Cursor 3まとめ:VS Code脱却、エージェントが98%書く時代のIDEはこう変わった

最後まで読んでくれてありがとうございます。

よければXもフォローしてもらえると嬉しいです → X(@morphox_ai)

会員登録して機能を使おう

この機能を利用するには、無料の会員登録が必要です。
お気に入りの記事を保存して、あとで読み返しましょう!