Ecommerce Template
Next.js·React 등으로 만든 최신 전자상거래 템플릿
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Verdant Lane is a production-minded e-commerce demo that connects a polished Next.js App Router storefront to a live Stripe catalogue and a durable Postgres order pipeline. It covers the complete purchase journey: product discovery, an accessible persistent cart, server-validated Stripe Checkout, signed webhooks, idempotent order state, and a protected itemized receipt.
Live demo: stripe-mini-shop.vercel.app
The project presents a fictional houseplant studio as a real, working online store. Stripe is the catalogue and payment source of truth, while Next.js owns the storefront experience and Postgres records payment outcomes. The codebase is deliberately small enough to study, but it handles the reliability and security boundaries that are commonly skipped in portfolio checkout demos.
| Project detail | Value |
|---|---|
| Format | Personal full-stack e-commerce case study |
| Status | Live on Vercel; Stripe operates in test mode |
| Scope | Product UI, cart state, server APIs, payment integration, database model, security hardening, tests, CI, and deployment |
| Core challenge | Keep the browser experience convenient without trusting browser-owned price, cart, receipt, or payment data |
| Production services | Vercel, Stripe, and Neon Postgres |
Short version
Verdant Lane is a production-minded Next.js storefront built around Stripe Checkout. It combines a live product catalogue, persistent cart, protected receipts, signed webhooks, and idempotent Postgres order processing with a polished responsive interface.
Extended version
I built Verdant Lane as an end-to-end e-commerce case study rather than a checkout mock-up. Products and current prices come from Stripe and are normalized into one cached sellable catalogue used by every page and API. Customers can browse responsive product pages, manage a persistent cart, apply an approved promotion code, and complete a real Stripe test-mode Checkout flow.
The server validates every product, price, quantity, currency, and redirect before creating a Checkout Session. Signed webhook events are recorded once in Postgres, order status can only advance safely, and a unique outbox record creates a boundary for future fulfillment work. Receipt details are available only to the browser that started the matching Checkout. The project also includes deterministic CI builds, unit and browser tests, typed environment validation, sanitized logging, CSP/HSTS headers, SEO metadata, and a live Vercel deployment.
Готовая версия для русскоязычного сайта
Verdant Lane — полнофункциональный демонстрационный интернет-магазин на Next.js со Stripe Checkout и надёжным серверным контуром. Каталог и актуальные цены загружаются из Stripe, корзина сохраняется в браузере, а сервер повторно проверяет товары, цены, валюту, количество и промокод перед созданием платёжной сессии.
Проект охватывает весь путь заказа: адаптивный каталог, карточки товаров, доступную корзину, тестовую оплату, защищённую квитанцию, подписанные webhook и идемпотентную запись состояния заказа в Postgres. Для стабильной разработки добавлены offline-fixtures, unit- и E2E-тесты, типизированная конфигурация, безопасное логирование, CSP/HSTS и CI с минимальными правами. Production-версия развёрнута на Vercel и работает с реальным тестовым каталогом Stripe.
sitemap.xml and robots.txt endpoints./api/checkout accepts a bounded request, applies
per-client and Stripe-call budgets, and checks every submitted price against
the same sellable catalogue shown in the UI. The client never decides the
authoritative amount or currency./success. A fabricated or leaked
Session ID causes a redirect before any Stripe receipt lookup.paid to regress to failed, and
inserts at most one order.paid outbox record for future fulfillment.flowchart LR
Browser["Browser / React UI"]
Catalogue["Next.js catalogue snapshot"]
Checkout["POST /api/checkout"]
Success["Protected /success receipt"]
Webhook["POST /api/stripe/webhook"]
Stripe["Stripe Products, Prices & Checkout"]
Postgres["Neon Postgres"]
Outbox["order.paid outbox"]
Stripe -->|"Products + default Prices"| Catalogue
Catalogue -->|"ISR pages + trusted price index"| Browser
Browser -->|"price IDs, quantities, email, promo"| Checkout
Checkout -->|"validated Session creation"| Stripe
Checkout -->|"Checkout URL + HttpOnly receipt proof"| Browser
Stripe -->|"hosted payment redirect"| Browser
Stripe -->|"signed events"| Webhook
Webhook -->|"atomic event/order update"| Postgres
Postgres --> Outbox
Browser -->|"Session ID + matching proof"| Success
Success -->|"verified receipt lookup"| Stripe
| Boundary | Current behavior |
|---|---|
| Catalogue consistency | Pages, sitemap, cart reconciliation, and Checkout eligibility consume the same validated SellableProduct snapshot. |
| Stripe availability | Catalogue calls use bounded pagination and three retry attempts with exponential backoff and jitter for 429/5xx responses. |
| Price integrity | Checkout accepts only the current active one-time default price of a displayed active product. Client-provided amounts are ignored. |
| Currency integrity | One configured storefront currency is enforced in the catalogue, persisted cart, UI totals, and Checkout API. |
| Resource bounds | Cart line count, item quantities, request bodies, tracked rate-limit buckets, and Stripe API work are explicitly capped. |
| Receipt privacy | A per-session HMAC proof is verified before Stripe is contacted, so a Checkout Session ID alone cannot expose order details. |
| Webhook authenticity | Stripe signatures are verified against the raw request body before event processing. |
| Event idempotency | Stripe event IDs are unique, paid state is monotonic, and (session_id, topic) prevents duplicate fulfillment outbox records. |
| Operational logging | Server errors pass through an allow-list serializer that omits raw SDK messages, headers, URLs, tokens, promo codes, and customer email. |
| Browser hardening | Production responses enforce a CSP without Stripe.js, frame-ancestors 'none', HSTS, nosniff, strict referrer policy, and a minimal permissions policy. |
| Configuration | Typed Zod profiles separate build, public, runtime, database, and secret variables; runtime-only secrets do not break ordinary builds. |
| CI trust boundary | Unit, E2E, and ordinary builds use deterministic fixtures with read-only repository permissions and no Stripe secret. |
The recent refactoring pass focused on making the demo safe to operate and predictable to change:
CatalogueRepository, one cached and
indexed catalogue snapshot, a single sellability invariant, and a strict
storefront currency policy shared by the UI and server.contents: read, moved
badge commits into a dedicated manual workflow, and kept PR code away from the
write-capable job.next/image, dynamic metadata, CSS Modules, Radix UI, and
Lucide icons.npm install
next and eslint-config-next are pinned to the same exact version and must
be upgraded together; Dependabot groups them into one PR.
Create .env.local (Next.js loads it automatically):
cp .env.example .env.local
Then adjust values for your Stripe workspace:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
RECEIPT_SIGNING_SECRET=replace_with_a_long_random_secret
DATABASE_URL=postgresql://user:password@host/database?sslmode=require
SITE_URL=http://localhost:3000
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_STOREFRONT_CURRENCY=USD
DEMO_SUCCESS=true
RATE_LIMIT_CHECKOUT_MAX=30
RATE_LIMIT_CHECKOUT_WINDOW_MS=60000
STRIPE_API_BUDGET_MAX=300
STRIPE_API_BUDGET_WINDOW_MS=60000
CHECKOUT_MAX_BODY_BYTES=16384
STRIPE_WEBHOOK_MAX_BODY_BYTES=1048576
SITE_URLis the trusted site origin used for Stripe Checkout redirect URLs, metadata,robots.txt, and the sitemap.NEXT_PUBLIC_SITE_URLis a fallback for it; when neither is set, the Vercel production URL is used.DEMO_SUCCESSis optional and only needed if you want to preview/successwithout a paid session outside of dev.DATABASE_URLis required only when processing payment webhooks; builds do not connect to it.RECEIPT_SIGNING_SECRETis optional but recommended. If it is omitted, the server derives receipt proofs fromSTRIPE_SECRET_KEY. On Vercel or Cloudflare Pages, the platform sets the source-IP environment flag used by the local defense-in-depth limiter. For multi-instance production, enable a shared edge/WAF rate limit on/api/checkout; the local limiter and Stripe API budget do not replace that shared boundary.
The deployed project uses Vercel. Following the
Vercel WAF rate-limiting guide,
open Project → Firewall and create a rule for
POST /api/checkout, keyed by IP, starting in Log mode and then enforcing a
fixed-window 429 after traffic has been observed. Start with 10 requests per
minute per IP and tune from production metrics. Keep Vercel's system DDoS
mitigation enabled.
Do not apply the Checkout's strict per-IP rule to /api/stripe/webhook: Stripe
deliveries are authenticated by their raw-body signature and can arrive in
bursts or retries. Stripe requires the
unmodified raw body for
signature verification. The route rejects missing signatures before reading the body
and caps signed payloads at STRIPE_WEBHOOK_MAX_BODY_BYTES. If a separate
webhook WAF rule is added, first run it in Log mode and use a limit high enough
not to discard legitimate deliveries.
Optional: seed test products via the helper script.
npx tsx scripts/seed-stripe.ts
npm run dev
Visit http://localhost:3000 and add products to your cart.
Provision a Postgres database through the
Vercel Storage Marketplace (the
Neon free plan is sufficient for this demo) and connect it to the project so
Vercel injects DATABASE_URL. For local webhook development, pull or copy that
connection string into .env.local. On the first signed payment webhook, the
application creates the stripe_events, orders, and order_outbox tables
with CREATE TABLE IF NOT EXISTS.
Use the Stripe CLI to forward events and capture the signing secret:
stripe listen --forward-to localhost:3000/api/stripe/webhook
Copy the printed whsec_... value into STRIPE_WEBHOOK_SECRET.
Create active promotion codes in your Stripe dashboard. Visitors can enter them in the cart; the API verifies a code before attaching it to the Checkout session. Stripe Checkout itself does not accept additional, unapproved codes.
Algorithm for viewing a successful payment flow:
session_id from the redirect URL./success?session_id=YOUR_SESSION_ID&preview=1.DEMO_SUCCESS=true./success?session_id=cs_test_...&preview=1
Without preview=1, the session must be paid and the browser must hold the matching signed receipt cookie created when it started Checkout; otherwise the app redirects to /cart without contacting Stripe.
| Command | Description |
|---|---|
npm run check | Lint, typecheck, Prettier check and unit tests in one pass |
npm run lint | ESLint rules (TypeScript-aware) |
npm run typecheck | tsc --noEmit over the app and tests |
npm run format:check | Prettier check without writing |
npm run test:unit | Vitest + React Testing Library |
npm run test:unit:coverage | Vitest with v8 coverage reports and enforced thresholds |
npm run build | Secretless deterministic production compilation using the catalogue fixture |
npm run build:stripe | Explicit live-Stripe build (protected integration environment only) |
npm run test:integration:stripe | Read-only, one-request Stripe test-account integration check |
npm run test:e2e:smoke | Fast Playwright subset (cart, not-found, success redirect checks) |
npm run test:e2e | Playwright scenarios (ensure browsers installed via npx playwright install) |
Playwright spins up the dev server automatically. With E2E_PRODUCTION=1 it
serves a prior npm run build through next start instead; CI runs the full
suite on main this way. Use npx playwright show-report to inspect the
latest run.
The Node version lives in .nvmrc (24) and every workflow reads it from there.
Unit tests, PR E2E, and ordinary builds never need a Stripe secret. They use the
versioned catalogue fixture in lib/catalogue-fixture.ts. A Vercel production
deployment selects the live Stripe catalogue explicitly; build:stripe is the
local equivalent. The separately named Stripe integration suite refuses live
keys and makes at most one read-only catalogue request.
All workflows have read-only repository permissions and never commit to main.
The Unit Tests and E2E Tests badges show the latest run of those workflows on
main; coverage comes from Codecov.
Coverage targets and the list of critical modules are documented in TESTING.md.
The CI runs both npm audit --omit=dev --audit-level=high and gitleaks scans.
If npm audit fails:
--omit=dev already filters dev tools).npm audit --omit=dev --audit-level=high locally before pushing.If gitleaks reports a false positive:
| Path | Purpose |
|---|---|
app/api/checkout/route.ts | Bounded cart validation, promotion lookup, and Checkout Session creation |
app/api/stripe/webhook/route.ts | Raw-body signature verification and payment-event mapping |
app/success/page.tsx | Receipt-proof authorization and paid Checkout receipt loading |
app/store/cart.ts | Versioned, sanitized cart persistence and derived totals |
lib/catalogue.ts | Sellability invariant, Stripe adapter, retry policy, and indexed snapshots |
lib/stripe.ts | Lazy Stripe client and cached catalogue accessors |
lib/order-store.ts | Transactional Stripe event, order state, and outbox persistence |
lib/receipt-proof.ts | Per-session HMAC receipt cookie creation and verification |
lib/server-log.ts | Redacted and sampled operational error serialization |
lib/config/env.ts | Typed build, public, runtime, database, and secret environment profiles |
lib/catalogue-fixture.ts | Deterministic catalogue for build, unit, and PR checks |
app/opengraph-image.tsx | Dynamic Open Graph preview generator |
app/sitemap.ts / app/robots.ts | SEO endpoints powered by the shared catalogue |
components/cart/**/* | Cart UI, Checkout form, cancellation summary, and success receipt |
localStorage.session_id.default_price.Deploy to Vercel (or any Next.js-compatible host), connect a Marketplace
Postgres database, and set the same environment variables
(STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, DATABASE_URL, SITE_URL,
NEXT_PUBLIC_SITE_URL, NEXT_PUBLIC_STOREFRONT_CURRENCY). Add a dedicated RECEIPT_SIGNING_SECRET for
independent receipt HMACs and DEMO_SUCCESS only if preview mode is intentionally
enabled outside development.
npm run build uses the deterministic fixture by default. The included build
wrapper selects the live Stripe catalogue only for a Vercel production context;
other hosts can opt in explicitly through npm run build:stripe.
Use the Stripe CLI or dashboard to point webhooks at the deployed URL (e.g., https://yourdomain.com/api/stripe/webhook).
Questions or ideas for improvements are welcome—feel free to open an issue or tweak the roadmap!