決済画面を「Isolated Checkout」にすべき理由と実装
はじめに
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 の全体構成と記事マップ