7.3 KiB
7.3 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-대규모-콘텐츠-기반-애플리케이션-및-전자상거래-플랫폼-구축 | 대규모 콘텐츠 기반 애플리케이션 및 전자상거래 플랫폼 구축 | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
대규모 콘텐츠 기반 애플리케이션 및 전자상거래 플랫폼 구축
매 한 줄
"매 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.
매 응용
- Shopify storefront (custom Hydrogen).
- Magazine + commerce hybrid.
- B2B catalog (gated pricing).
- Marketplace (multi-vendor).
💻 패턴
Next.js 15 PPR (Partial Prerendering) for PDP
// 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 (
<>
<ProductGallery images={product.images} />
<ProductInfo product={product} />
<Suspense fallback={<PriceSkeleton />}>
<DynamicPrice productId={product.id} /> {/* 매 per-user / region */}
</Suspense>
<Suspense fallback={<RecsSkeleton />}>
<Recommendations productId={product.id} />
</Suspense>
</>
);
}
export async function generateStaticParams() {
const top = await getTopProducts(1000);
return top.map(p => ({ slug: p.slug }));
}
ISR with on-demand revalidation
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
import { InstantSearch, SearchBox, Hits, RefinementList } from 'react-instantsearch';
import { liteClient } from 'algoliasearch/lite';
const client = liteClient(APP_ID, SEARCH_KEY);
<InstantSearch searchClient={client} indexName="products">
<SearchBox />
<RefinementList attribute="brand" />
<RefinementList attribute="category" />
<Hits hitComponent={ProductCard} />
</InstantSearch>
Headless CMS content (Sanity)
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)
'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
// 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 (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>
</body>
</html>
);
}
Image optimization at scale
import Image from 'next/image';
<Image
src={product.image}
alt={product.name}
width={800}
height={800}
sizes="(max-width: 768px) 100vw, 50vw"
priority={isAboveFold}
placeholder="blur"
blurDataURL={product.imageBlur}
/>
Edge runtime for personalization
// 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
'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 |