Tech Blog

決済画面を「Isolated Checkout」にすべき理由と実装

Vue3 UX TypeScript Frontend

はじめに

EC サイトの決済画面を設計するとき、「ヘッダーやナビゲーションはどうするか」という問題があります。

「通常のヘッダーをそのまま使えばいい」と思いがちですが、
Amazon・Stripe Checkout・楽天などの大手ECは、決済画面では通常のナビゲーションを非表示にします。

これを Isolated Checkout(隔離されたチェックアウト) と呼びます。


なぜ決済画面を隔離するのか

理由1: ユーザーの離脱を防ぐ

決済フロー中にナビゲーションが見えると、ユーザーが「あ、そういえば別の商品も見てこよう」と離脱します。

カゴ落ち(Cart Abandonment)の原因の一つは、決済フロー中の余計なリンクです。

理由2: 信頼感とセキュリティ感の演出

決済画面がシンプルであるほど、「今は支払い処理をしている」という意識がユーザーに生まれます。
雑多なナビが見えているより、「ここはセキュアな画面」と感じてもらえます。

理由3: ミス操作の防止

「戻る」を押すつもりがナビのリンクをクリックしてしまう、
フォームを入力中に検索バーにフォーカスが移る、といった事故を防ぎます。


Amazon の Isolated Checkout

Amazon の決済フローを確認すると:

  • 通常のヘッダー(検索欄・カートアイコン・メニュー)→ 非表示
  • シンプルなヘッダーに amazon ロゴのみ → 表示
  • フッターも最小限(カスタマーサービスリンクのみ)

これが Isolated Checkout の典型的なデザインです。


Vue 3 での実装

ルーター設定でレイアウトを切り替える

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  // 通常ページ(標準ヘッダー・フッター付き)
  {
    path: '/',
    component: () => import('@/layouts/DefaultLayout.vue'),
    children: [
      { path: '', component: () => import('@/views/HomeView.vue') },
      { path: 'films', component: () => import('@/views/FilmsView.vue') },
    ],
  },
  
  // 決済ページ(Isolated Checkout レイアウト)
  {
    path: '/checkout',
    component: () => import('@/layouts/CheckoutLayout.vue'),  // ← 別レイアウト
    children: [
      { path: '', component: () => import('@/views/CheckoutView.vue') },
      { path: 'confirm', component: () => import('@/views/CheckoutConfirmView.vue') },
      { path: 'complete', component: () => import('@/views/CheckoutCompleteView.vue') },
    ],
  },
]

DefaultLayout.vue(通常ページ)

<!-- layouts/DefaultLayout.vue -->
<template>
  <div class="layout-default">
    <AppHeader />     <!-- 検索バー・カート・ナビあり -->
    <main>
      <RouterView />
    </main>
    <AppFooter />
  </div>
</template>

CheckoutLayout.vue(決済ページ)

<!-- layouts/CheckoutLayout.vue -->
<template>
  <div class="layout-checkout">
    <!-- シンプルなヘッダー:ロゴのみ -->
    <header class="checkout-header">
      <div class="checkout-header-inner">
        <RouterLink to="/" class="checkout-logo">
          DVDレンタル
        </RouterLink>
        <!-- ナビなし、検索なし、カートアイコンなし -->
        <div class="checkout-security-badge">
          <i class="bi bi-shield-lock-fill" />
          <span>セキュアな決済</span>
        </div>
      </div>
    </header>
    
    <main class="checkout-main">
      <RouterView />
    </main>
    
    <!-- フッターは最小限 -->
    <footer class="checkout-footer">
      <a href="/help">ヘルプ</a>
      <a href="/privacy">プライバシーポリシー</a>
    </footer>
  </div>
</template>

<style scoped>
.checkout-header {
  border-bottom: 1px solid #ddd;
  padding: 12px 0;
  background: #fff;
}

.checkout-header-inner {
  max-width: 800px;
  margin: 0 auto;
  padding: 0 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.checkout-logo {
  font-size: 1.4rem;
  font-weight: bold;
  color: #333;
  text-decoration: none;
}

.checkout-security-badge {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #28a745;
  font-size: 0.9rem;
}

.checkout-main {
  max-width: 800px;
  margin: 0 auto;
  padding: 32px 16px;
}

.checkout-footer {
  border-top: 1px solid #ddd;
  padding: 16px;
  text-align: center;
  font-size: 0.85rem;
  color: #666;
}

.checkout-footer a {
  margin: 0 12px;
  color: #666;
}
</style>

決済フローのステップ表示

決済フロー内では、どのステップにいるかを示すプログレスバーを表示します。

<!-- CheckoutProgressBar.vue -->
<template>
  <div class="checkout-progress">
    <div
      v-for="step in steps"
      :key="step.id"
      class="step"
      :class="{
        'step--active': currentStep === step.id,
        'step--completed': currentStep > step.id,
      }"
    >
      <div class="step-number">{{ step.id }}</div>
      <div class="step-label">{{ step.label }}</div>
    </div>
  </div>
</template>

<script setup lang="ts">
defineProps<{ currentStep: number }>()

const steps = [
  { id: 1, label: '内容確認' },
  { id: 2, label: 'お支払い' },
  { id: 3, label: '完了' },
]
</script>

「戻る」ボタンの設計

Isolated Checkout では、ブラウザの「戻る」ボタンで決済フローを離脱できます。
これは意図通りの動作です。

ただし、決済確認・完了画面では「戻る」で再送信が起きないよう注意:

// CheckoutCompleteView.vue
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

onMounted(() => {
  // 完了画面で戻るボタンを押してもカート等に戻すため
  // history を置き換えて「戻る」でトップへ
  router.replace('/checkout/complete')
})

まとめ

通常ページ決済ページ(Isolated Checkout)
通常のヘッダー(検索・ナビ)ロゴのみのシンプルヘッダー
フル機能のフッターリンク最小限のフッター
回遊を促すUI決済完了への集中を促すUI

Vue 3 では レイアウトコンポーネントの切り替えを使うことで、
ルーティングレベルでレイアウトを分離できます。

「決済画面だけ別レイアウト」という実装は数十行で済み、
ユーザーの完了率(コンバージョン)にも影響する重要なUX設計です。


このアプリでの実装

このDVDレンタルアプリでは Vue Router を使わず、currentPage ref によるシングルファイル方式で Isolated Checkout を実現しています。

isCheckoutFlow の判定(実際のコード)

// App.vue
const currentPage = ref<
  'films' | 'cd-rentals' | 'streaming' | 'ranking' | 'feature' |
  'detail' | 'auth' | 'mypage' | 'member-register' | 'member-register-confirm' |
  'member-edit' | 'checkout' | 'checkout-complete' | ...
>('films')

// 決済フロー中かどうかの判定
const isCheckoutFlow = computed(() => {
  return currentPage.value === 'checkout' || currentPage.value === 'checkout-complete'
})

テンプレートでのレイアウト切り替え(実際のコード)

<!-- App.vue -->

<!-- 通常ヘッダー(決済フロー中は非表示) -->
<header class="masthead" v-if="!isCheckoutFlow">
  <!-- ロゴ・カートアイコン・ナビゲーション等 -->
</header>

<!-- 決済専用ヘッダー(決済フロー中のみ表示) -->
<header class="checkout-masthead" v-if="isCheckoutFlow">
  <!-- ロゴのみ・セキュリティバッジ -->
</header>

<!-- グローバルナビ(決済フロー中は非表示) -->
<nav class="global-tabs" v-if="!isCheckoutFlow">
  <!-- 作品一覧・ランキング等タブ -->
</nav>

<!-- フッター(決済フロー中は非表示) -->
<footer class="site-footer" v-if="!isCheckoutFlow">
  <!-- 通常フッター -->
</footer>

Vue Router のレイアウトルーティングと比べて記述量は少なく、 v-if="!isCheckoutFlow" を通常UI要素に付けるだけでヘッダー・ナビ・フッターが一括非表示になります。

今すぐ購入 → 決済画面への遷移(実際のコード)

// App.vue
const selectedFilm = ref<PublicFilmSummary | null>(null)

const handleDirectCheckout = (film: PublicFilmSummary) => {
  selectedFilm.value = film   // 決済対象の作品を保持
  currentPage.value = 'checkout'  // Isolated Checkout へ切り替え
}

実際の画面

決済画面(Isolated Checkout)。ヘッダーはロゴのみ、ナビゲーションなし:

決済画面


このシリーズの記事マップ

dvdrental 管理アプリと対になるエンドユーザー向けDVDレンタルアプリを作っている話 — Vue 3 + Spring Boot の全体構成と記事マップ

気軽にメッセージください

技術相談・ご感想・ご質問があればメッセージをお願いします。