CONTENT
ここから
この章でわかること
この章では、商品ごとの詳細ページを作ります。
第5章では、商品一覧ページを作りました。
商品一覧ページは、商品を見つけてもらう場所です。
一方で、商品詳細ページは、
この商品を買うかどうかを判断してもらう場所です。
そのため、商品詳細ページには、一覧ページよりも多くの情報を載せます。
商品画像
商品名
価格
商品説明
在庫状態
配送情報
購入ボタン
関連商品
SEO情報
構造化データ
攻略本でいうと、商品一覧ページは「ショップの棚」です。
商品詳細ページは「商品を手に取って説明を聞く場所」です。
ここで不安が減れば、購入ボタンへ進んでもらいやすくなります。
6.1 Dynamic Routeで商品詳細ページを作る
商品詳細ページは、商品ごとにURLが変わります。
たとえば、次のようなURLです。
/products/classic-dashi
/products/gift-dashi-set
/products/limited-box
このように、URLの一部が商品ごとに変わるページには、Dynamic Routeを使います。
Next.js公式ドキュメントでは、事前に正確なルート名がわからない場合や、動的データからルートを生成したい場合にDynamic Segmentsを使うと説明されています。[slug]のようなフォルダ名を使うことで、動的なURLを作れます。(nextjs.org)
商品詳細ページのファイル構成は次のようになります。
src
└─ app
└─ products
├─ page.tsx
└─ [slug]
└─ page.tsx
[slug]が、商品ごとに変わる部分です。
/products/classic-dashi
↓
slug = classic-dashi
/products/gift-dashi-set
↓
slug = gift-dashi-set
商品を取得する関数を作る
まず、商品データからslugに一致する商品を探す関数を作ります。
src/data/products.tsに追加します。
import type { Product } from '@/types/product';
export const products: Product[] = [
{
id: 'product_001',
slug: 'classic-dashi',
name: 'クラシックだし',
description: '毎日の味噌汁や煮物に使いやすい、香り高いだしパックです。',
price: 1200,
currency: 'JPY',
imageUrl: '/images/products/classic-dashi.jpg',
isAvailable: true,
},
{
id: 'product_002',
slug: 'gift-dashi-set',
name: 'ギフトだしセット',
description: '大切な方への贈り物に使いやすい、上品なギフトセットです。',
price: 3500,
currency: 'JPY',
imageUrl: '/images/products/gift-dashi-set.jpg',
isAvailable: false,
},
];
/**
* 役割: URLのslugから該当する商品を取得する
* 入力: 商品URLに使うslug
* 出力: 一致するProduct。存在しない場合はundefined
*/
export function getProductBySlug(slug: string): Product | undefined {
return products.find((product) => product.slug === slug);
}
商品詳細ページを作る
src/app/products/[slug]/page.tsxを作ります。
import { notFound } from 'next/navigation';
import { getProductBySlug } from '@/data/products';
import { formatPrice } from '@/utils/formatPrice';
type ProductDetailPageProps = {
params: Promise<{
slug: string;
}>;
};
/**
* 役割: 商品slugに応じた商品詳細ページを表示する
* 入力: URLパラメータとして渡される商品slug
* 出力: 商品名、説明、価格、販売状態を含む商品詳細ページ
*/
export default async function ProductDetailPage({
params,
}: ProductDetailPageProps) {
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product) {
notFound();
}
return (
<main className="mx-auto max-w-6xl px-4 py-12">
<div className="grid gap-10 lg:grid-cols-2">
<div className="rounded-2xl bg-gray-100 p-8">
<p className="text-sm text-gray-500">商品画像エリア</p>
</div>
<section className="space-y-6">
<div className="space-y-3">
<p className="text-sm font-semibold text-gray-500">Product</p>
<h1 className="text-3xl font-bold tracking-tight text-gray-900">
{product.name}
</h1>
<p className="text-xl font-semibold text-gray-900">
{formatPrice(product.price, product.currency)}
</p>
</div>
<p className="leading-8 text-gray-700">{product.description}</p>
<p className="text-sm font-semibold">
{product.isAvailable ? '販売中' : '現在売り切れです'}
</p>
</section>
</div>
</main>
);
}
攻略ポイント
Dynamic Routeは、商品ページを自動で増やす仕組みです。
商品データが増える
↓
slugが増える
↓
商品詳細ページも増える
1商品ごとに手作業でページを作る必要はありません。
6.2 商品スラッグの設計
スラッグとは、URLに使う文字列です。
たとえば、次のURLではclassic-dashiがスラッグです。
/products/classic-dashi
スラッグは、商品ページの住所のようなものです。
一度公開したURLは、できるだけ変えないほうがよいです。
理由は、検索エンジンやSNS、外部リンクに影響するからです。
よいスラッグ
classic-dashi
gift-dashi-set
limited-summer-box
organic-green-tea
避けたいスラッグ
product1
item-abc
新商品
テスト商品
20260706item
日本語スラッグも技術的には使えますが、URL共有時にエンコードされて読みにくくなる場合があります。
そのため、本書では英小文字とハイフンを使います。
使う文字
↓
英小文字
数字
ハイフン
避ける文字
↓
スペース
記号
絵文字
長すぎる文字列
スラッグ設計のルール
短くする
商品内容がわかる
英小文字にする
単語はハイフンでつなぐ
公開後はなるべく変えない
攻略ポイント
スラッグは、商品ページの名前ではなく、住所です。
住所を頻繁に変えると、お客様も検索エンジンも迷います。
商品名
↓
変更してもよい
slug
↓
なるべく変更しない
商品名の表現を後から変える可能性がある場合でも、スラッグは安定した名前にしておきます。
6.3 商品説明、価格、在庫、配送情報の表示
商品詳細ページでは、購入判断に必要な情報を表示します。
一覧ページでは、情報を絞りました。
詳細ページでは、もう少し丁寧に伝えます。
何の商品か
いくらか
今買えるか
いつ届くか
どう使うか
誰に向いているか
Product型を拡張する
配送情報や詳しい説明を表示するために、商品型を少し拡張します。
src/types/product.tsを更新します。
export type Product = {
id: string;
slug: string;
name: string;
shortDescription: string;
description: string;
price: number;
currency: 'JPY';
imageUrl: string;
isAvailable: boolean;
stockLabel: string;
shippingLabel: string;
};
商品データの例です。
export const products: Product[] = [
{
id: 'product_001',
slug: 'classic-dashi',
name: 'クラシックだし',
shortDescription: '毎日の味噌汁や煮物に使いやすい、香り高いだしパックです。',
description:
'クラシックだしは、日々の料理に使いやすいだしパックです。味噌汁、煮物、うどん、炊き込みご飯など、家庭料理に自然になじみます。',
price: 1200,
currency: 'JPY',
imageUrl: '/images/products/classic-dashi.jpg',
isAvailable: true,
stockLabel: '在庫あり',
shippingLabel: '通常3営業日以内に発送',
},
];
詳細ページに表示する
<div className="space-y-4 rounded-xl border border-gray-200 p-4">
<div className="flex items-center justify-between gap-4">
<span className="text-sm text-gray-500">在庫状況</span>
<span className="text-sm font-semibold text-gray-900">
{product.stockLabel}
</span>
</div>
<div className="flex items-center justify-between gap-4">
<span className="text-sm text-gray-500">配送目安</span>
<span className="text-sm font-semibold text-gray-900">
{product.shippingLabel}
</span>
</div>
</div>
表示する情報の優先順位
商品詳細ページでは、何でも載せればよいわけではありません。
購入前に必要な情報を、見やすい順番で並べます。
1. 商品画像
2. 商品名
3. 価格
4. 短い説明
5. 在庫状態
6. 配送情報
7. 購入ボタン
8. 詳しい説明
9. FAQ
攻略ポイント
商品詳細ページの役割は、購入前の不安を減らすことです。
何の商品かわからない
↓
説明する
いくらかわからない
↓
価格をはっきり出す
買えるかわからない
↓
在庫状態を出す
いつ届くかわからない
↓
配送目安を出す
お客様が迷う理由を、ページの中で先に解消します。
6.4 購入ボタンの設計
商品詳細ページで最も重要なパーツが、購入ボタンです。
購入ボタンは、ただのボタンではありません。
商品ページから決済へ進むための入口です。
最初は、購入ボタンを押したらSquareの決済ページへ進む構成にします。
商品詳細ページ
↓
購入ボタン
↓
Next.jsのAPI
↓
Square決済ページ
購入ボタンの基本設計
購入ボタンでは、次の状態を考えます。
販売中
↓
購入ボタンを表示する
売り切れ
↓
購入ボタンを無効化する
処理中
↓
ボタンを押せないようにする
src/components/product/BuyButton.tsxを作ります。
'use client';
import { useState } from 'react';
type BuyButtonProps = {
productId: string;
isAvailable: boolean;
};
/**
* 役割: 商品購入処理を開始するボタンを表示する
* 入力: 商品ID、販売可能状態
* 出力: 販売中なら購入ボタン、売り切れなら無効化されたボタン
*/
export function BuyButton({ productId, isAvailable }: BuyButtonProps) {
const [isLoading, setIsLoading] = useState(false);
async function handleClick(): Promise<void> {
if (!isAvailable || isLoading) {
return;
}
setIsLoading(true);
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ productId }),
});
if (!response.ok) {
throw new Error('決済ページの作成に失敗しました。');
}
const data: unknown = await response.json();
if (!isCheckoutResponse(data)) {
throw new Error('決済ページURLの形式が正しくありません。');
}
window.location.href = data.checkoutUrl;
} catch {
setIsLoading(false);
alert('購入処理を開始できませんでした。時間をおいて再度お試しください。');
}
}
return (
<button
type="button"
onClick={handleClick}
disabled={!isAvailable || isLoading}
className="w-full rounded-md bg-black px-6 py-3 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:bg-gray-300"
>
{isLoading ? '決済ページを準備中...' : isAvailable ? '購入する' : '売り切れ'}
</button>
);
}
type CheckoutResponse = {
checkoutUrl: string;
};
/**
* 役割: APIレスポンスが決済URLを含む形式か確認する
* 入力: unknown型のAPIレスポンス
* 出力: checkoutUrlを持つCheckoutResponseかどうか
*/
function isCheckoutResponse(value: unknown): value is CheckoutResponse {
if (typeof value !== 'object' || value === null) {
return false;
}
return (
'checkoutUrl' in value &&
typeof (value as { checkoutUrl: unknown }).checkoutUrl === 'string'
);
}
ここでは、まだ/api/checkoutの中身は作りません。
第7章でSquare Checkout APIと接続します。
商品詳細ページで使う
import { BuyButton } from '@/components/product/BuyButton';
<BuyButton productId={product.id} isAvailable={product.isAvailable} />;
攻略ポイント
購入ボタンでは、ブラウザ側の情報を信用しすぎてはいけません。
たとえば、ブラウザ側でproductIdや価格を書き換えられる可能性があります。
そのため、決済金額や在庫状態は、必ずサーバー側で再確認します。
ブラウザ側
↓
購入したい商品IDを送る
サーバー側
↓
商品IDから正しい価格を取得する
在庫状態を確認する
Square決済リンクを作る
購入ボタンは入口です。
正しい判断はサーバー側で行います。
6.5 メタデータを商品ごとに生成する
商品詳細ページでは、商品ごとにSEO情報を変える必要があります。
たとえば、商品名が違えば、ページタイトルも変わります。
/products/classic-dashi
↓
クラシックだし|商品詳細
/products/gift-dashi-set
↓
ギフトだしセット|商品詳細
Next.jsでは、動的なメタデータをgenerateMetadataで生成できます。
Next.js公式ドキュメントでは、静的なmetadataオブジェクトまたは動的なgenerateMetadata関数を使って、SEOやWeb共有向けのメタデータを定義できると説明されています。generateMetadataは動的ルートのparamsなどを受け取り、ページごとのメタデータ生成に利用できます。(nextjs.org, nextjs.org)
src/app/products/[slug]/page.tsxに追加します。
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { getProductBySlug } from '@/data/products';
import { formatPrice } from '@/utils/formatPrice';
type ProductDetailPageProps = {
params: Promise<{
slug: string;
}>;
};
export async function generateMetadata({
params,
}: ProductDetailPageProps): Promise<Metadata> {
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product) {
return {
title: '商品が見つかりません',
};
}
return {
title: `${product.name}|商品詳細`,
description: product.shortDescription,
openGraph: {
title: `${product.name}|商品詳細`,
description: product.shortDescription,
images: [
{
url: product.imageUrl,
alt: product.name,
},
],
},
};
}
メタデータで設定するもの
商品ページでは、最低限次の情報を設定します。
title
description
openGraph.title
openGraph.description
openGraph.images
SNSやLINEで商品ページが共有されたとき、OGP画像やタイトルが表示されます。
そのため、商品ごとに適切なメタデータを出すことが大切です。
攻略ポイント
商品詳細ページは、検索とSNSの入口にもなります。
検索結果
↓
titleとdescriptionが大切
SNS共有
↓
OGP画像とタイトルが大切
商品ページ内
↓
本文と購入ボタンが大切
ページの中身だけでなく、外から見えたときの印象も整えます。
6.6 構造化データを追加する
構造化データとは、検索エンジンやAIにページの意味を伝えるためのデータです。
Next.js公式ドキュメントでは、JSON-LDは検索エンジンやAIがページ構造を理解するために使える構造化データ形式だと説明されています。また、Next.jsではscriptタグでapplication/ld+jsonを出力する実装例が紹介されています。(nextjs.org)
商品ページでは、商品名、説明、画像、価格、在庫状態などを構造化データとして出せます。
JSON-LDを作る
商品詳細ページ内で、JSON-LDを作ります。
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.shortDescription,
image: product.imageUrl,
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: product.currency,
availability: product.isAvailable
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
},
};
ページ内に出力します。
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
}}
/>
どこに入れるか
商品詳細ページのreturnの中に入れます。
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
}}
/>
<main className="mx-auto max-w-6xl px-4 py-12">
{/* 商品詳細UI */}
</main>
</>
);
注意点
構造化データは、検索順位を必ず上げる魔法ではありません。
あくまで、検索エンジンにページ内容を正しく伝えるための補助です。
また、ページに表示していない内容を構造化データだけに入れるのは避けます。
よい例
↓
ページに表示している商品名、価格、在庫状態をJSON-LDにも入れる
悪い例
↓
ページに存在しないレビュー評価や架空の在庫情報を入れる
攻略ポイント
構造化データは、検索エンジン向けの説明書です。
ユーザー向け
↓
ページ本文
検索エンジン向け
↓
JSON-LD
両方の内容が矛盾しないようにします。
6.7 関連商品を表示する
商品詳細ページでは、関連商品を表示すると回遊しやすくなります。
お客様がその商品を買わなかったとしても、別の商品に興味を持ってもらえる可能性があります。
商品詳細を見る
↓
少し違うと感じる
↓
関連商品を見る
↓
別の商品に進む
最初は、同じ商品以外を数件表示するだけでも十分です。
関連商品を取得する関数
src/data/products.tsに追加します。
/**
* 役割: 現在の商品を除外して関連商品を取得する
* 入力: 現在表示している商品ID、取得件数
* 出力: 関連商品として表示するProduct配列
*/
export function getRelatedProducts(
currentProductId: string,
limit: number,
): Product[] {
return products
.filter((product) => product.id !== currentProductId)
.slice(0, limit);
}
詳細ページで表示する
import { ProductCard } from '@/components/product/ProductCard';
import { getProductBySlug, getRelatedProducts } from '@/data/products';
const relatedProducts = getRelatedProducts(product.id, 3);
表示部分です。
{relatedProducts.length > 0 && (
<section className="mt-16 space-y-6">
<div className="space-y-2">
<p className="text-sm font-semibold text-gray-500">Related Products</p>
<h2 className="text-2xl font-bold tracking-tight text-gray-900">
関連商品
</h2>
</div>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{relatedProducts.map((relatedProduct) => (
<ProductCard key={relatedProduct.id} product={relatedProduct} />
))}
</div>
</section>
)}
関連商品の選び方
最初は単純で大丈夫です。
現在の商品以外を表示する
同じカテゴリの商品を表示する
価格帯が近い商品を表示する
ギフト商品を表示する
売れ筋商品を表示する
商品数が増えてきたら、カテゴリやタグを使って関連商品を選びます。
攻略ポイント
関連商品は、押し売りではなく案内です。
この商品が違うかもしれない
↓
では、こちらもあります
商品詳細ページからの離脱を減らすための導線として使います。
6.8 商品詳細ページの読み込み速度を上げる
商品詳細ページは、速く表示されることが大切です。
特にECでは、ページ表示が遅いと購入前に離脱されやすくなります。
Next.jsでは、動的なルートを事前生成するためにgenerateStaticParamsを使えます。公式ドキュメントでは、Dynamic Route Segmentsと組み合わせて、リクエスト時ではなくビルド時にルートを静的生成できると説明されています。(nextjs.org)
generateStaticParamsを使う
商品データが静的に決まっている場合は、商品詳細ページをビルド時に生成できます。
src/app/products/[slug]/page.tsxに追加します。
import { products } from '@/data/products';
export function generateStaticParams(): Array<{ slug: string }> {
return products.map((product) => ({
slug: product.slug,
}));
}
これにより、商品ごとのページを事前に用意しやすくなります。
products配列
↓
各slugを取得
↓
商品詳細ページを事前生成
画像を最適化する
商品詳細ページでは、大きな商品画像を表示します。
ここでもnext/imageを使います。
Next.js公式ドキュメントでは、ImageコンポーネントはHTMLのimg要素を拡張し、画像の自動最適化を提供すると説明されています。また、画像読み込み時のレイアウトシフトを防ぐため、サイズ指定やfill利用時の親要素設定が重要です。(nextjs.org, nextjs.org)
import Image from 'next/image';
<div className="relative aspect-square overflow-hidden rounded-2xl bg-gray-100">
<Image
src={product.imageUrl}
alt={product.name}
fill
priority
sizes="(min-width: 1024px) 50vw, 100vw"
className="object-cover"
/>
</div>
ファーストビューに出るメイン画像には、必要に応じてpriorityを指定します。
ただし、すべての画像にpriorityを付けるわけではありません。
メイン画像
↓
priorityを検討する
関連商品画像
↓
通常読み込みでよい
Client Componentを増やしすぎない
商品詳細ページの本文や商品説明は、Server Componentで表示します。
Client Componentにするのは、購入ボタンのようにユーザー操作が必要な部分だけです。
Server Component
↓
商品名、説明、価格、画像、配送情報
Client Component
↓
購入ボタン、数量変更、カート操作
Client Componentを増やしすぎると、ブラウザで動くJavaScriptが増え、ページが重くなる可能性があります。
攻略ポイント
商品詳細ページの高速化は、次の3つを意識します。
商品ページを事前生成する
画像を最適化する
Client Componentを必要最小限にする
特に、商品説明のように変わりにくい情報は、なるべく軽く表示します。
一方で、在庫や購入処理のように正確性が必要な部分は、購入ボタンを押したタイミングでサーバー側確認を行います。
完成イメージ
ここまでの内容を組み合わせると、商品詳細ページは次のような構成になります。
import type { Metadata } from 'next';
import Image from 'next/image';
import { notFound } from 'next/navigation';
import { BuyButton } from '@/components/product/BuyButton';
import { ProductCard } from '@/components/product/ProductCard';
import {
getProductBySlug,
getRelatedProducts,
products,
} from '@/data/products';
import { formatPrice } from '@/utils/formatPrice';
type ProductDetailPageProps = {
params: Promise<{
slug: string;
}>;
};
export function generateStaticParams(): Array<{ slug: string }> {
return products.map((product) => ({
slug: product.slug,
}));
}
export async function generateMetadata({
params,
}: ProductDetailPageProps): Promise<Metadata> {
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product) {
return {
title: '商品が見つかりません',
};
}
return {
title: `${product.name}|商品詳細`,
description: product.shortDescription,
openGraph: {
title: `${product.name}|商品詳細`,
description: product.shortDescription,
images: [
{
url: product.imageUrl,
alt: product.name,
},
],
},
};
}
/**
* 役割: 商品slugに応じた商品詳細ページを表示する
* 入力: URLパラメータとして渡される商品slug
* 出力: 商品画像、商品説明、価格、在庫、配送情報、購入ボタン、関連商品を含むページ
*/
export default async function ProductDetailPage({
params,
}: ProductDetailPageProps) {
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product) {
notFound();
}
const relatedProducts = getRelatedProducts(product.id, 3);
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.shortDescription,
image: product.imageUrl,
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: product.currency,
availability: product.isAvailable
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
},
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
}}
/>
<main className="mx-auto max-w-6xl px-4 py-12">
<div className="grid gap-10 lg:grid-cols-2">
<div className="relative aspect-square overflow-hidden rounded-2xl bg-gray-100">
<Image
src={product.imageUrl}
alt={product.name}
fill
priority
sizes="(min-width: 1024px) 50vw, 100vw"
className="object-cover"
/>
</div>
<section className="space-y-6">
<div className="space-y-3">
<p className="text-sm font-semibold text-gray-500">Product</p>
<h1 className="text-3xl font-bold tracking-tight text-gray-900">
{product.name}
</h1>
<p className="text-2xl font-semibold text-gray-900">
{formatPrice(product.price, product.currency)}
</p>
</div>
<p className="leading-8 text-gray-700">{product.description}</p>
<div className="space-y-4 rounded-xl border border-gray-200 p-4">
<div className="flex items-center justify-between gap-4">
<span className="text-sm text-gray-500">在庫状況</span>
<span className="text-sm font-semibold text-gray-900">
{product.stockLabel}
</span>
</div>
<div className="flex items-center justify-between gap-4">
<span className="text-sm text-gray-500">配送目安</span>
<span className="text-sm font-semibold text-gray-900">
{product.shippingLabel}
</span>
</div>
</div>
<BuyButton
productId={product.id}
isAvailable={product.isAvailable}
/>
</section>
</div>
{relatedProducts.length > 0 && (
<section className="mt-16 space-y-6">
<div className="space-y-2">
<p className="text-sm font-semibold text-gray-500">
Related Products
</p>
<h2 className="text-2xl font-bold tracking-tight text-gray-900">
関連商品
</h2>
</div>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{relatedProducts.map((relatedProduct) => (
<ProductCard key={relatedProduct.id} product={relatedProduct} />
))}
</div>
</section>
)}
</main>
</>
);
}
この章のまとめ
この章では、商品詳細ページを作りました。
商品一覧ページは、商品を探してもらう場所です。
商品詳細ページは、購入を判断してもらう場所です。
この章で作った内容は、次の通りです。
Dynamic Routeで商品ごとのページを作る
slugから商品データを取得する
商品説明、価格、在庫、配送情報を表示する
購入ボタンを設計する
商品ごとにメタデータを生成する
JSON-LDで構造化データを追加する
関連商品を表示する
generateStaticParamsで読み込み速度を上げる
画像をnext/imageで最適化する
商品詳細ページでは、ユーザーの不安を減らすことが大切です。
何の商品か
いくらか
買えるのか
いつ届くのか
どこから購入するのか
これらがわかりやすく表示されていると、購入ボタンへ進んでもらいやすくなります。
また、商品詳細ページはSEOやSNS共有の入口にもなります。
そのため、generateMetadataやJSON-LDを使って、ページの意味を外部にも正しく伝えます。
次の章でやること
次の章では、いよいよSquare Checkout APIを使って購入導線を作ります。
これまでは、商品を表示するところまで作りました。
次は、購入ボタンを押したときにSquareの決済ページを作成し、ユーザーを決済へ進めます。
ここから、ECサイトが実際に「販売できる仕組み」へ近づいていきます。