SaaS 가격 페이지 UX: 전환율을 높이는 플랜 비교 설계

UX 디자인

가격 페이지SaaS UX플랜 비교전환율 최적화구독 설계

이 글은 누구를 위한 것인가

  • SaaS 가격 페이지 전환율이 낮아 고민하는 팀
  • 플랜이 몇 개여야 하는지, 어떻게 비교해야 하는지 모르는 팀
  • 기업 고객과 개인 고객을 동시에 대응하는 가격 체계를 설계하는 팀

들어가며

가격 페이지는 "선택 스트레스"를 최소화하는 것이 핵심이다. 플랜이 너무 많으면 선택 마비가 온다. 추천 플랜을 명확히 하이라이트하고, 연간 할인의 혜택을 직관적으로 보여줘야 한다.

이 글은 bluefoxdev.kr의 SaaS UX 가이드 를 참고하여 작성했습니다.


1. 가격 페이지 설계 원칙

[플랜 수 최적화]

2개 플랜:
  명확한 선택, 적은 혼란
  문제: 중간 가격대 없어 이탈

3개 플랜 (최적):
  Free/Starter/Pro 또는 Basic/Pro/Enterprise
  중간 플랜이 가장 많이 선택됨 (타협 효과)
  추천: 가운데 플랜 하이라이트

4개+ 플랜:
  선택 피로도 증가
  개인 + 팀 + 기업 등 구분 필요한 경우만

[연간/월간 토글 전략]
  연간 선택 시 할인 강조: "2개월 무료"가 "16% 할인"보다 강함
  연간이 기본 선택으로 (더 많은 수익)
  월간은 회색 처리

[추천 플랜 강조]
  Popular 배지 또는 Recommended 배지
  카드 크기 살짝 크게 또는 배경색 차별화
  → 사용자의 결정을 도와주는 앵커 역할
  
[FAQ 필수 항목]
  무료 체험 이후 자동 결제 여부
  해지 방법과 환불 정책
  팀 인원 추가 방법
  엔터프라이즈 맞춤 요금

2. 가격 페이지 컴포넌트

"use client";
import { useState } from "react";

const PLANS = [
  {
    id: "starter",
    name: "Starter",
    monthlyPrice: 0,
    yearlyPrice: 0,
    description: "개인 프로젝트 시작",
    features: ["프로젝트 3개", "5GB 스토리지", "이메일 지원"],
    cta: "무료로 시작",
    ctaVariant: "outline" as const,
  },
  {
    id: "pro",
    name: "Pro",
    monthlyPrice: 29000,
    yearlyPrice: 24000,
    description: "성장하는 팀을 위한",
    features: ["무제한 프로젝트", "100GB 스토리지", "우선 지원", "API 액세스", "팀 최대 10명"],
    cta: "14일 무료 체험",
    ctaVariant: "primary" as const,
    recommended: true,
  },
  {
    id: "enterprise",
    name: "Enterprise",
    monthlyPrice: null,
    yearlyPrice: null,
    description: "대규모 조직을 위한",
    features: ["Pro 모든 기능", "무제한 팀 멤버", "SSO/SAML", "SLA 보장", "전담 매니저"],
    cta: "영업팀 문의",
    ctaVariant: "outline" as const,
  },
];

function PricingPage() {
  const [isYearly, setIsYearly] = useState(true);

  return (
    <div className="max-w-5xl mx-auto px-4 py-16">
      <div className="text-center mb-12">
        <h1 className="text-4xl font-bold mb-4">투명한 가격 정책</h1>
        <p className="text-gray-500 text-lg">비즈니스 성장에 맞게 언제든 변경하세요</p>
        
        {/* 연간/월간 토글 */}
        <div className="inline-flex items-center gap-3 bg-gray-100 rounded-full p-1 mt-6">
          <button
            onClick={() => setIsYearly(false)}
            className={`px-4 py-2 rounded-full text-sm font-medium transition-all ${
              !isYearly ? "bg-white shadow" : "text-gray-500"
            }`}
          >
            월간
          </button>
          <button
            onClick={() => setIsYearly(true)}
            className={`px-4 py-2 rounded-full text-sm font-medium transition-all ${
              isYearly ? "bg-white shadow" : "text-gray-500"
            }`}
          >
            연간
            <span className="ml-1.5 text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">
              2개월 무료
            </span>
          </button>
        </div>
      </div>

      {/* 플랜 카드 */}
      <div className="grid md:grid-cols-3 gap-6">
        {PLANS.map((plan) => (
          <div
            key={plan.id}
            className={`rounded-2xl border p-8 relative ${
              plan.recommended
                ? "border-blue-600 bg-blue-600 text-white scale-105 shadow-xl"
                : "border-gray-200 bg-white"
            }`}
          >
            {plan.recommended && (
              <div className="absolute -top-3 left-1/2 -translate-x-1/2">
                <span className="bg-yellow-400 text-yellow-900 text-xs font-bold px-3 py-1 rounded-full">
                  가장 인기
                </span>
              </div>
            )}

            <h3 className="text-xl font-bold mb-1">{plan.name}</h3>
            <p className={`text-sm mb-6 ${plan.recommended ? "text-blue-200" : "text-gray-500"}`}>
              {plan.description}
            </p>

            {plan.monthlyPrice !== null ? (
              <div className="mb-8">
                <span className="text-4xl font-bold">
                  {(isYearly ? plan.yearlyPrice : plan.monthlyPrice).toLocaleString()}원
                </span>
                <span className={`text-sm ${plan.recommended ? "text-blue-200" : "text-gray-400"}`}>
                  /월
                </span>
                {isYearly && plan.yearlyPrice > 0 && (
                  <p className={`text-xs mt-1 ${plan.recommended ? "text-blue-200" : "text-gray-400"}`}>
                    연 {(plan.yearlyPrice * 12).toLocaleString()}원 청구
                  </p>
                )}
              </div>
            ) : (
              <div className="text-3xl font-bold mb-8">문의</div>
            )}

            <ul className="space-y-3 mb-8">
              {plan.features.map((feature) => (
                <li key={feature} className="flex items-center gap-2 text-sm">
                  <span>✓</span> {feature}
                </li>
              ))}
            </ul>

            <a
              href={plan.id === "enterprise" ? "/contact" : "/signup"}
              className={`block w-full py-3 rounded-xl font-semibold text-center ${
                plan.ctaVariant === "primary"
                  ? "bg-white text-blue-600 hover:bg-blue-50"
                  : plan.recommended
                  ? "border-2 border-white text-white hover:bg-blue-700"
                  : "border-2 border-gray-200 hover:border-blue-600 hover:text-blue-600"
              }`}
            >
              {plan.cta}
            </a>
          </div>
        ))}
      </div>
    </div>
  );
}

마무리

가격 페이지에서 가장 강력한 UX는 "추천 플랜 하이라이트"와 "2개월 무료(연간)" 조합이다. 사용자는 혼자 결정하기 어려울 때 앵커(권장 선택)를 찾는다. 가격을 숨기거나 "문의하세요"만 남기면 전환율이 크게 떨어지므로, 최소한 기본 플랜은 명확한 가격을 보여주는 것이 좋다.