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개월 무료(연간)" 조합이다. 사용자는 혼자 결정하기 어려울 때 앵커(권장 선택)를 찾는다. 가격을 숨기거나 "문의하세요"만 남기면 전환율이 크게 떨어지므로, 최소한 기본 플랜은 명확한 가격을 보여주는 것이 좋다.