Files
2nd/10_Wiki/Topic_Programming/Frontend/대규모 콘텐츠 기반 애플리케이션 및 전자상거래 플랫폼 구축.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

236 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 (
<>
<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
```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);
<InstantSearch searchClient={client} indexName="products">
<SearchBox />
<RefinementList attribute="brand" />
<RefinementList attribute="category" />
<Hits hitComponent={ProductCard} />
</InstantSearch>
```
### 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 (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>
</body>
</html>
);
}
```
### Image optimization at scale
```tsx
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
```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
- 부모: [[Large_Frontend_Projects|Frontend Architecture]] · [[Web Performance]]
- 변형: [[Headless Commerce]]
- 응용: [[Sanity]] · [[Algolia]]
- Adjacent: [[ISR]] · [[Edge Runtime]]
## 🤖 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 |