--- id: wiki-2026-0508-대규모-콘텐츠-기반-애플리케이션-및-전자상거래-플랫폼-구축 title: 대규모 콘텐츠 기반 애플리케이션 및 전자상거래 플랫폼 구축 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [E-commerce Platform, Content Platform, Headless Commerce] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [frontend, ecommerce, content, nextjs, headless] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: nextjs --- # 대규모 콘텐츠 기반 애플리케이션 및 전자상거래 플랫폼 구축 ## 매 한 줄 > **"매 SEO + 속도 + 개인화 의 trilemma"**. 백만 SKU + CMS 콘텐츠 + 개인화 추천 을 동시에 빠르게 보여줘야 한다. 2026 답: Next.js 15 PPR + ISR + Edge runtime + headless CMS (Sanity/Contentful) + Stripe/Shopify Hydrogen. ## 매 핵심 ### 매 대규모 commerce 의 도전 - **카탈로그**: 100k-1M SKU — search/filter 빠르게. - **SEO**: PDP (product detail page) 가 organic traffic 핵심 — SSR/ISR 필수. - **개인화**: 추천, cart, region pricing — per-user dynamic. - **트래픽 spike**: BFCM (Black Friday) 10-100× 평소. - **글로벌**: i18n, currency, tax, shipping zone. ### 매 architecture 패턴 - **Headless commerce**: storefront (Next.js) + commerce engine (Shopify/Commerce Layer/Saleor) 분리. - **Composable**: CMS + commerce + search (Algolia) + reviews + auth — 매 best-of-breed. - **Static + dynamic mix**: PDP는 ISR, cart/checkout 은 dynamic, 매 PPR 로 한 page 안에서 mix. ### 매 응용 1. Shopify storefront (custom Hydrogen). 2. Magazine + commerce hybrid. 3. B2B catalog (gated pricing). 4. Marketplace (multi-vendor). ## 💻 패턴 ### Next.js 15 PPR (Partial Prerendering) for PDP ```tsx // app/products/[slug]/page.tsx export const experimental_ppr = true; export default async function PDP({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; const product = await getProduct(slug); // 매 static (ISR) return ( <> }> {/* 매 per-user / region */} }> ); } export async function generateStaticParams() { const top = await getTopProducts(1000); return top.map(p => ({ slug: p.slug })); } ``` ### ISR with on-demand revalidation ```ts export const revalidate = 3600; // 매 1h auto-revalidate // app/api/revalidate/route.ts — webhook from CMS export async function POST(req: Request) { const { slug, secret } = await req.json(); if (secret !== process.env.REVALIDATE_SECRET) return new Response('forbidden', { status: 403 }); revalidateTag(`product-${slug}`); return Response.json({ revalidated: true }); } // in fetch fetch(`${API}/products/${slug}`, { next: { tags: [`product-${slug}`] } }); ``` ### Algolia search with InstantSearch ```tsx import { InstantSearch, SearchBox, Hits, RefinementList } from 'react-instantsearch'; import { liteClient } from 'algoliasearch/lite'; const client = liteClient(APP_ID, SEARCH_KEY); ``` ### Headless CMS content (Sanity) ```ts import { groq } from 'next-sanity'; const POST_QUERY = groq`*[_type == "post" && slug.current == $slug][0]{ title, body, "author": author->name, publishedAt }`; export async function getPost(slug: string) { return client.fetch(POST_QUERY, { slug }, { next: { tags: [`post-${slug}`] } }); } ``` ### Shopping cart (server actions + cookie) ```ts 'use server'; import { cookies } from 'next/headers'; export async function addToCart(variantId: string, qty: number) { const cartId = (await cookies()).get('cart_id')?.value; const cart = cartId ? await shopify.cart.add(cartId, variantId, qty) : await shopify.cart.create(variantId, qty); (await cookies()).set('cart_id', cart.id, { httpOnly: true, secure: true }); revalidateTag('cart'); } ``` ### i18n with next-intl ```tsx // app/[locale]/layout.tsx import { NextIntlClientProvider } from 'next-intl'; import { getMessages } from 'next-intl/server'; export default async function Layout({ children, params }: { children: ReactNode, params: Promise<{ locale: string }> }) { const { locale } = await params; const messages = await getMessages(); return ( {children} ); } ``` ### Image optimization at scale ```tsx import Image from 'next/image'; {product.name} ``` ### Edge runtime for personalization ```ts // middleware.ts import { NextResponse } from 'next/server'; export function middleware(req: NextRequest) { const country = req.geo?.country ?? 'US'; const res = NextResponse.next(); res.cookies.set('region', country); return res; } ``` ### Web Vitals tracking ```tsx 'use client'; import { useReportWebVitals } from 'next/web-vitals'; export function Analytics() { useReportWebVitals(metric => { fetch('/api/vitals', { method: 'POST', body: JSON.stringify(metric) }); }); return null; } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | < 1k SKU | static-only + on-demand ISR. | | 1k-100k SKU | ISR top-N + on-demand for tail. | | 1M+ SKU | dynamic + heavy CDN cache. | | Personalized price | PPR — static shell + dynamic price hole. | | Search | Algolia / Meilisearch / Typesense. | | BFCM-scale | edge cache + queue checkout. | **기본값**: Next.js 15 + PPR + ISR + Algolia + Shopify Storefront API + Stripe. ## 🔗 Graph - 부모: [[Frontend Architecture]] · [[Web Performance]] - 변형: [[Headless CMS]] · [[Headless Commerce]] · [[JAMstack]] - 응용: [[Shopify Hydrogen]] · [[Sanity]] · [[Algolia]] - Adjacent: [[Next.js PPR]] · [[ISR]] · [[Edge Runtime]] · [[Stripe]] ## 🤖 LLM 활용 **언제**: 콘텐츠 + 거래 동시 제공, SEO 핵심, 100+ pages. **언제 X**: SPA dashboard, 매 small custom shop (Shopify default theme 으로 충분). ## ❌ 안티패턴 - **Full SSR for everything**: PDP 매 request 마다 DB hit → ISR + tag-based revalidate. - **No CDN for images**: origin 직접 serve → Vercel Image / Cloudflare Images / imgix. - **Client-side cart in localStorage only**: 매 device 간 sync 안 됨 → server cart + cookie id. - **Synchronous 3rd-party scripts**: GTM, A/B → 매 next/script strategy="lazyOnload". - **Hardcoded i18n strings**: 시작부터 namespace 분리. - **No cache stampede protection**: revalidate 시 동시 1000 request → SWR / lock. ## 🧪 검증 / 중복 - Verified (Next.js 15 docs, Vercel commerce template, Shopify Hydrogen, web.dev/commerce). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Next.js 15 PPR + headless commerce stack |