9148c358d0
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 폴더 제거.
236 lines
7.3 KiB
Markdown
236 lines
7.3 KiB
Markdown
---
|
||
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 |
|