【Next.js】料金表の作り方を解説|3プランと月額・年額の切替
公開日: 2026年9月8日 · 読了目安 14 分
Next.js の LP に置く料金表(プライシングセクション)の作り方を、プランのデータ設計、3プランのカード、月額・年額の切替トグルの順にコード付きで解説します。Next.js 自体の利用料金を知りたい方への短い答えも冒頭にまとめています。
Next.js の料金表は3プランとおすすめ1つで作る
SaaS やツールの LP を Next.js で作っていて、料金のセクションまで来た。
プランをどう並べれば読者が迷わないか、月額と年額をどう切り替えるか。この記事は、そこで手が止まった人に向けた実装ガイドです。
なお「Next.js 料金」で調べて来た方へ先にお答えすると、Next.js 自体は MIT ライセンスの無料のオープンソースです。
費用がかかるのは、Vercel などのホスティング先のプランです。料金は各社の公式ページで最新のものを確認してください。
この記事は、自分のサイトに載せる料金表の作り方を扱います。
答えは次のとおりです。プランは3つまでにし、本命の1つだけを目立たせます。
プランの内容は配列のデータにまとめます。月額と年額の切替は、料金セクションのファイルだけをブラウザで動く部品(クライアントコンポーネント)にして持たせます。
プランのデータを型付きの配列にまとめる方法
3プランのカードを並べるコード(サーバーコンポーネント)
月額・年額の切替トグルのコードと、切替時のレイアウト崩れの防ぎ方
スマホ表示、アクセシビリティ、よくある失敗
読者を迷わせない料金表に共通する3つの条件
料金表を見た人が次のボタンを押せないと、ヒーローから積み上げてきた説明が無駄になります。読まれる料金表には、共通する条件が3つあります。
1つ目は、プランが3つまでであることです。4つ以上を同じ大きさのカードで並べると、差が見えにくくなり全部が中途半端に見えます。
LP では「入口・本命・上位」の3つが扱いやすい形です。
2つ目は、おすすめが1つだけあることです。同じ枠と同じボタンのカードが並ぶと、読者は一番安い欄だけを見ます。
本命のカードだけ枠線を濃くし、「おすすめ」のラベルを1つ付ければ十分です。
3つ目は、価格とその注記が近くにあることです。注記とは「税込」「年額は一括請求」といった情報です。
個人のお客様向けの価格は、税込の総額で表示する決まりがあります(法人向けの取引は対象外)。
こうした注記がフッターに離れていると、読者は数字を信用できません。価格のすぐ下に1行で書きます。
LP に置く料金表の完成イメージと構成
この記事で作る料金表は、上から次の順に並びます。
短い見出しと1行の説明
月払い・年払いの切替トグル
3枚のカード(左が入口、中央が本命、右が上位)
各カードに、プラン名・1行の説明・価格・注記・機能5〜7行・ボタン
本命のカードだけ濃い枠線と「おすすめ」のラベル
機能を何十行も並べる比較表(マトリクス)は扱いません。まずは、スクロールしてきた人が3つから1つを選べる画面を作ります。
プラン間の細かい比較が必要な場合は、カードの下に表を足します。比較表の作り方は https://design-layer.com/ja/articles/nextjs-comparison-table-guide にまとめています。
Step1:料金表のプランをデータにまとめる
最初に、表示より先にデータを決めます。月額と年額でカードを別々に書くと、片方だけ修正し忘れてずれます。
1つのプランに月額と年額の両方を持たせ、表示する側で選ぶ形にします。
export type Plan = {
id: string;
name: string;
description: string;
monthly: number; // 月払いの月額(税込)
yearly: number; // 年払いの年額(税込)
features: string[];
featured?: boolean;
cta: { label: string; href: string };
};
// 金額はサンプルです
export const plans: Plan[] = [
{
id: "starter",
name: "Starter",
description: "個人で小さく始める",
monthly: 980,
yearly: 9800,
features: ["プロジェクト3件", "基本テンプレート", "メールサポート"],
cta: { label: "Starter で始める", href: "/signup?plan=starter" },
},
{
id: "pro",
name: "Pro",
description: "仕事で毎日使う",
monthly: 1980,
yearly: 19800,
features: ["プロジェクト無制限", "すべてのテンプレート", "優先サポート"],
featured: true,
cta: { label: "Pro で始める", href: "/signup?plan=pro" },
},
{
id: "team",
name: "Team",
description: "チームで共有する",
monthly: 9800,
yearly: 98000,
features: ["メンバー10人まで", "権限の管理", "請求書払い"],
cta: { label: "Team について相談する", href: "/contact" },
},
];
export const yen = (n: number) => "¥" + n.toLocaleString("ja-JP");料金表のボタンの行き先もプランごとに持つ
cta をデータに含めておくと、プランごとに行き先を変えられます。
全部のボタンが同じ「お問い合わせ」に飛ぶなら、カードを分ける意味は薄れます。上位プランだけ「相談する」にするなど、行き先と文言を合わせます。
Step2:3プランの料金表カードを並べる
月額だけを表示するなら、サーバーで組み立てる部品(サーバーコンポーネント)のまま作れます。グリッドはスマホで1列、md 以上で3列です。
カードを flex の縦並びにし、機能リストに flex-1 を付けると、機能の行数が違ってもボタンの位置が下端で揃います。
import { plans, yen } from "@/lib/plans";
export function PricingCards() {
return (
<section className="px-6 py-24">
<h2 className="text-center text-3xl font-semibold">料金プラン</h2>
<p className="mt-3 text-center text-zinc-600">どのプランも14日間は無料で試せます。</p>
<div className="mx-auto mt-12 grid max-w-5xl gap-6 md:grid-cols-3">
{plans.map((plan) => (
<article
key={plan.id}
className={
"flex flex-col rounded-2xl p-6 " +
(plan.featured ? "border-2 border-zinc-900" : "border border-zinc-200")
}
>
{plan.featured ? (
<p className="w-fit rounded-full bg-zinc-900 px-3 py-1 text-xs text-white">おすすめ</p>
) : null}
<h3 className="mt-3 text-lg font-semibold">{plan.name}</h3>
<p className="mt-1 text-sm text-zinc-600">{plan.description}</p>
<p className="mt-6 text-3xl font-semibold tabular-nums">
{yen(plan.monthly)}
<span className="text-sm font-normal text-zinc-500"> /月</span>
</p>
<p className="mt-1 text-xs text-zinc-500">税込</p>
<ul className="mt-6 flex-1 space-y-2 text-sm">
{plan.features.map((f) => (
<li key={f}>
<span aria-hidden="true">✓ </span>
{f}
</li>
))}
</ul>
<a
href={plan.cta.href}
className="mt-8 inline-flex justify-center rounded-full bg-zinc-950 py-3 text-sm text-white"
>
{plan.cta.label}
</a>
</article>
))}
</div>
</section>
);
}料金表の本命プランは枠線とラベルで目立たせる
本命のカードを scale-110 などで大きくすると、PC では目立ちます。ただ、スマホでは隣のカードと重なったり、はみ出したりします。
濃い枠線と短いラベル1つで十分に伝わります。3枚とも同じスタイルにすると、料金表ではなくカタログに見えます。
サンプルの説明文は自分のサービスに合わせる
見出しの下の「14日間は無料」はコードの例です。無料期間がないサービスなら、「年払いで2か月分お得」など、選ぶ判断に役立つ一文に置き換えてください。
Step3:料金表に月額・年額の切替トグルを付ける
年払いの割引を見せたくなったら、いま表示している周期を state(部品が覚えておく値)に持ちます。
ブラウザで動かすのは、料金セクションのファイルだけです。このファイルの先頭に "use client"(この部品はブラウザで動かす、という印)を書きます。
ページ本体の page.tsx は、サーバーで組み立てる部品のままにします。プランのデータは、部品に渡す値(props)として渡します。
"use client";
import { useState } from "react";
import { yen, type Plan } from "@/lib/plans";
type Cycle = "monthly" | "yearly";
export function PricingSection({ plans }: { plans: Plan[] }) {
const [cycle, setCycle] = useState<Cycle>("monthly");
return (
<section className="px-6 py-24 text-center">
<h2 className="text-3xl font-semibold">料金プラン</h2>
<div role="group" aria-label="支払いの周期" className="mt-8 inline-flex rounded-full border p-1">
{(["monthly", "yearly"] as const).map((c) => (
<button
key={c}
type="button"
aria-pressed={cycle === c}
onClick={() => setCycle(c)}
className={
"rounded-full px-4 py-2 text-sm " +
(cycle === c ? "bg-zinc-950 text-white" : "text-zinc-600")
}
>
{c === "monthly" ? "月払い" : "年払い(2か月分お得)"}
</button>
))}
</div>
<div className="mx-auto mt-12 grid max-w-5xl gap-6 text-left md:grid-cols-3">
{plans.map((plan) => (
<article
key={plan.id}
className={
"flex flex-col rounded-2xl p-6 " +
(plan.featured ? "border-2 border-zinc-900" : "border border-zinc-200")
}
>
<h3 className="text-lg font-semibold">{plan.name}</h3>
<p className="mt-6 text-3xl font-semibold tabular-nums">
{yen(cycle === "monthly" ? plan.monthly : plan.yearly)}
<span className="text-sm font-normal text-zinc-500">
{cycle === "monthly" ? " /月" : " /年"}
</span>
</p>
<p className="mt-1 text-xs text-zinc-500">
{cycle === "monthly" ? "税込・毎月請求" : "税込・1年分を一括請求"}
</p>
<ul className="mt-6 flex-1 space-y-2 text-sm">
{plan.features.map((f) => (
<li key={f}>{f}</li>
))}
</ul>
<a
href={plan.cta.href + (plan.cta.href.includes("?") ? "&" : "?") + "cycle=" + cycle}
className="mt-8 inline-flex justify-center rounded-full bg-zinc-950 py-3 text-sm text-white"
>
{plan.cta.label}
</a>
</article>
))}
</div>
</section>
);
}
// app/page.tsx(サーバーコンポーネントのまま)
// import { plans } from "@/lib/plans";
// <PricingSection plans={plans} />切替でレイアウトが動かないようにする
月額の ¥1,980 と年額の ¥19,800 では桁数が違います。数字の幅が変わってカードががたつかないよう、価格に tabular-nums を付けて数字の幅を揃えます。
注記の行も両方の周期で1行に収め、カードの高さが切替のたびに跳ねないようにします。
年払いのお得さは数字で書く
「年払いがお得」だけでは比べられません。「2か月分お得」「約17%お得」のように、計算した結果を書きます。
年額を12で割った月あたりの金額を小さく添える方法もよく使われます。
申し込みボタンに料金の周期を引き継ぐ
年払いを選んだ人が申し込み画面で月払いに戻されると、離脱につながります。上のコードでは、リンクの URL に cycle を付けて引き継いでいます。
href にすでにクエリがあれば & で、なければ ? でつなぎます。申し込み画面の側でも、この値を読んで初期の周期を合わせてください。
料金表のスマホ表示とアクセシビリティ
PC で3列に並ぶ料金表は、スマホでは縦に3枚積まれます。最初の画面に入るのは1枚目だけなので、何を最初に見せるかを決めておきます。
本命を先に見せたいなら、本命のカードに order-first md:order-none を付ける
3枚を横スクロールだけで見せる作りにしない。2枚目以降に気づかれにくい
価格は画像にせずテキストで書く。読み上げにも検索にも残る
切替トグルはボタン要素で作る。aria-pressed(押されているかを伝える属性)で、今どちらを表示しているかを読み上げに伝える
おすすめは色だけでなく「おすすめ」という文字でも伝える
料金表でよくある失敗と直し方
最安のプランを中央に置く。本命が分からなくなる。入口は左、本命は中央が読みやすい
機能を20行書く。各プランで違う行だけを残し、共通の機能は「すべてのプランに含む」としてカードの下にまとめる
ボタンを「詳細を見る」にする。料金表の役目は次の一歩なので、「Pro で始める」「相談する」のように行動を書く
料金を出さずに「要問い合わせ」のカードを3枚並べる。価格を出せないなら、料金セクション自体を置かない方がすっきりする
無料プランを別ページに逃がす。有料プランの隣にないと比べられない
完成した料金表をコピーして使う方法

見た目の作り込みまで含めると、Step3 の state を書いたあとも調整に時間がかかります。たとえば次のような部分です。
周期の切替トグル
おすすめの枠
機能のチェックリスト
暗い背景の3カード
その場合は、完成した料金セクションをブラウザで実際に動かして確かめる方法もあります。React と Tailwind のコードを、そのまま自分の Next.js に載せます。
DesignLayer のカタログでは、セクションをライブプレビューで確認し、Copy for Cursor で AI エージェントに渡せます。
閲覧にカードは不要で、無料のソースコピーは日本時間で1日3回です。カタログは https://design-layer.com/ja/components から開けます。
載せたあとの仕事は、プラン名・金額・機能・ボタンの差し替えです。SaaS やサブスクの LP に向いています。
案件ごとに見積もる受託や、席数や使った量で価格が変わるサービスには向かないので、「目安」と問い合わせで見せる方が正直です。
料金表についてよくある質問
Next.js 自体は MIT ライセンスのオープンソースで、無料で使えます。費用がかかるのは、Vercel などのホスティング先です。
料金や無料枠の範囲は変わることがあるので、各社の公式ページで確認してください。
まとめ:Next.js で料金表を作る手順
Next.js 自体は無料のオープンソース。費用はホスティング先で決まる
料金表はプラン3つまで、本命1つだけを枠線とラベルで目立たせる
プランは月額と年額を1つのデータに持たせ、表示側で選ぶ
切替は料金セクションのファイルだけをブラウザで動かし、価格の数字の幅を揃えてがたつきを防ぐ
税込・一括請求などの注記は価格のすぐ下に書く
まずは Step1 と Step2 のコードで3枚のカードを表示してみてください。
料金表の直後には FAQ を置くと、価格を見た人の不安をそのまま解消できます。作り方は https://design-layer.com/ja/articles/nextjs-faq-accordion-guide 、SaaS の LP 全体の構成は https://design-layer.com/ja/articles/nextjs-saas-landing-page-guide で解説しています。


