[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -2,104 +2,240 @@
|
||||
id: wiki-2026-0508-saas-대시보드-및-이커머스-레이아웃-구축
|
||||
title: SaaS 대시보드 및 이커머스 레이아웃 구축
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: []
|
||||
aliases: [SaaS Dashboard Layout, Ecommerce Layout, Admin Layout]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [uncategorized]
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [frontend, layout, saas, dashboard, ecommerce, nextjs, shadcn]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-08
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
language: TypeScript
|
||||
framework: Next.js 16 / Tailwind v4 / shadcn-ui
|
||||
---
|
||||
|
||||
# [[SaaS 대시보드 및 이커머스 레이아웃 구축]]
|
||||
# SaaS 대시보드 및 이커머스 레이아웃 구축
|
||||
|
||||
## 📌[[ brief]] 시 Summary
|
||||
[[SaaS]] 대시보드 및 이커머스 레이아웃 구축은 방대한 데이터와 제품 정보를 다양한 크기의 화면에서 일관되고 직관적으로 보여주기 위한 반응형 설계 과정입니다. 이를 위해 [[CSS Grid]]와 [[Flexbox]]를 결합하여 견고한 레이아웃을 구성하고, 컨테이너 쿼리([[Container Queries]])를 통해 컴포넌트 단위의 유연성을 확보합니다. 또한, 시각적 계층 구조를 명확히 하고 상호작용에 대한 즉각적인 피드백을 제공하기 위해 의도적인 애니메이션(Motion Design)을 전략적으로 적용하여 유지보수성과 사용자 경험(UX)을 동시에 향상시킵니다.
|
||||
## 매 한 줄
|
||||
> **"매 SaaS 의 sidebar+content shell + 매 Ecommerce 의 PLP/PDP/Cart"**. 2026 stack 매 Next.js 16 (App Router) + Tailwind v4 + shadcn-ui + Radix Primitive + TanStack Query. 매 Server Component 로 data fetch, Client Component 로 interactivity.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
**1. SaaS 대시보드 레이아웃 및 스타일링 전략**
|
||||
* **CSS Grid를 통한 2차원 배치:** 대시보드는 다양한 위젯과 데이터 구조를 한 화면에 배치해야 하므로, 행과 열을 동시에 정밀하게 제어할 수 있는 CSS Grid가 매우 적합합니다 [1].
|
||||
* **컨테이너 쿼리를 활용한 데이터 반응성:** 분석 대시보드나 CRM 등 데이터가 많은 인터페이스는 화면이 좁아질 때 표와 차트가 자연스럽게 축소되지 않는 문제를 겪습니다 [2]. 이를 해결하기 위해 전체 화면 너비가 아닌 '부모 컨테이너의 너비'를 기준으로 반응하는 컨테이너 쿼리를 사용합니다 [3, 4]. 좁은 너비에서는 막대 차트를 단순한 숫자 카드로 변경하거나, 데이터 테이블의 각 행을 라벨이 붙은 카드 스택으로 변환하는 방식이 최선의 구현으로 꼽힙니다 [2].
|
||||
* **애니메이션을 통한 데이터 시각화 강화:** 대시보드에서 카드와 차트를 독립적으로 움직이게 하는 계층형 모션(Layered Motion)을 적용하면, 배경 패널보다 전경의 핵심 지표를 약간 빠르게 이동시켜 중요한 정보로 사용자의 시선을 유도할 수 있습니다 [5]. 또한 애니메이션이 적용된 데이터 시각화를 통해 복잡한 정보와 변화 추이를 사용자가 빠르게 소화할 수 있도록 돕습니다 [6].
|
||||
## 매 핵심
|
||||
|
||||
**2. 이커머스 레이아웃 및 UI 구축 전략**
|
||||
* **모바일 중심(Mobile-First) 구조와 콘텐츠 재배치:** 선도적인 이커머스 인터페이스는 모든 중단점(Breakpoint)에서 제품 이미지를 최우선에 두고 가격, 리뷰, 옵션, CTA(콜투액션) 등의 지원 콘텐츠를 그 주위에 재배치합니다 [7]. 모바일 환경에서는 스크롤을 하더라도 항상 접근할 수 있도록 구매 버튼을 뷰포트 하단에 고정하며, 필터 및 정렬 컨트롤은 사이드바 대신 전체 화면 오버레이나 하단 시트 패턴으로 축소하여 제공합니다 [7].
|
||||
* **카드 기반 레이아웃 (Card-Based Layouts):** 제품 목록에는 카드 기반 레이아웃을 사용하는 것이 가장 신뢰할 수 있는 패턴입니다 [8]. 큰 화면에서는 여러 카드가 나란히 놓이지만, 작은 화면에서는 레이아웃이 깨지지 않고 수직으로 자연스럽게 쌓이므로 탐색이 매우 용이해집니다 [8].
|
||||
* **사용자 피드백을 위한 마이크로 인터랙션:** 장바구니에 제품을 추가할 때 짧은 애니메이션으로 장바구니 아이콘을 강조하면, 사용자의 현재 탐색 흐름을 방해하지 않으면서 작업이 완료되었음을 명확히 피드백할 수 있습니다 [9]. 주문 후 결제 등 시간이 걸리는 과정에서는 스켈레톤 스크린(Skeleton screens)과 같은 로딩 애니메이션을 배치하여 체감 대기 시간을 줄이고 시스템 상태에 대한 확신을 줍니다 [10].
|
||||
### 매 SaaS Dashboard 패턴
|
||||
- **Shell**: sidebar (collapsible) + topbar (search/user menu) + content area.
|
||||
- **Navigation**: 매 nested section, persistent state, breadcrumb.
|
||||
- **Data widgets**: 매 stat card, chart (Recharts/Tremor v3), table (TanStack Table).
|
||||
- **Multi-tenant**: 매 workspace switcher.
|
||||
|
||||
**3. 유지보수성과 성능을 고려한 구조 설계**
|
||||
* **역할에 따른 Flexbox와 Grid 분리:** 페이지의 전체적인 골격이나 뼈대(대규모 레이아웃)를 잡을 때는 CSS Grid를 사용하고, 그 내부 컴포넌트 요소들을 정렬할 때는 Flexbox를 혼합하여 사용하는 것이 효율적이고 유지보수하기 좋은 아키텍처를 만듭니다 [11, 12].
|
||||
* **애니메이션 성능 최적화:** 대시보드나 이커머스와 같이 렌더링할 컴포넌트가 많은 곳에서 너비(width), 높이(height), 여백(margin) 등 레이아웃에 영향을 주는 속성을 애니메이션화하면 값비싼 리플로우(Reflow)와 리페인트(Repaint)가 발생하여 성능이 저하됩니다 [13, 14]. 따라서 렌더링 성능을 최적화하기 위해서는 GPU 가속을 활용할 수 있는 `transform`이나 `opacity` 위주로 애니메이션을 설계해야 합니다 [14-16].
|
||||
### 매 Ecommerce 패턴
|
||||
- **PLP (Product List Page)**: 매 grid + filter sidebar + sort + pagination/infinite scroll.
|
||||
- **PDP (Product Detail Page)**: 매 image gallery + variant selector + add-to-cart.
|
||||
- **Cart/Checkout**: 매 cart drawer + multi-step checkout + Stripe Elements.
|
||||
- **Search**: 매 typeahead + facet filtering (Algolia/MeiliSearch).
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- **Related Topics:** [[CSS Grid]], [[Flexbox]], [[컨테이너 쿼리 (Container Queries)]], 모바일 중심 설계 ([[Mobile-First Design]]), 마이크로 인터랙션 ([[Micro-interactions]])
|
||||
- **Projects/Contexts:** 데이터 중심 대시보드 (Data-heavy Dashboards), 반응형 이커머스 웹사이트 (Responsive E-commerce Websites)
|
||||
- **Contradictions/Notes:** 뷰포트 기반의 일반적인 미디어 쿼리(Media Queries)는 사이드바나 영웅 영역 등 컴포넌트가 배치된 개별 컨텍스트의 가용 공간을 파악하지 못하는 근본적인 한계가 있습니다. 따라서 대시보드나 이커머스처럼 복잡하고 모듈화된 레이아웃에서는 화면 크기가 아닌 컴포넌트 부모 크기에 반응하는 컨테이너 쿼리가 2026년의 새로운 표준으로 권장됩니다 [3].
|
||||
### 매 Stack (2026)
|
||||
- Framework: Next.js 16 App Router / Remix 3 / Astro 5.
|
||||
- UI: shadcn-ui v2 + Radix + Tailwind v4 (Oxide engine).
|
||||
- Data: TanStack Query 5 + Server Action / RSC.
|
||||
- Forms: react-hook-form + Zod.
|
||||
- Tables: TanStack Table v8.
|
||||
- Charts: Tremor v3 / Recharts.
|
||||
|
||||
---
|
||||
*Last updated: 2026-04-26*
|
||||
### 매 응용
|
||||
1. SaaS admin: Linear-style sidebar.
|
||||
2. Ecommerce: Shopify Hydrogen / Next.js Commerce 템플릿.
|
||||
3. Internal tool: Retool-like 매 form/table heavy.
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
## 💻 패턴
|
||||
|
||||
> *(TODO: 한 문장으로 핵심 통찰을 작성. "X는 Y 조건에서 Z 효과를 낸다" 구조 권장.)*
|
||||
### App Router Layout (Next.js 16)
|
||||
```tsx
|
||||
// app/(dashboard)/layout.tsx
|
||||
import { Sidebar } from '@/components/sidebar';
|
||||
import { Topbar } from '@/components/topbar';
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
|
||||
- **과거 데이터와의 충돌:** 없음
|
||||
- **정책 변화:** 없음
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
const user = await getCurrentUser();
|
||||
return (
|
||||
<div className="grid h-screen grid-cols-[260px_1fr]">
|
||||
<Sidebar />
|
||||
<div className="flex flex-col overflow-hidden">
|
||||
<Topbar user={user} />
|
||||
<main className="flex-1 overflow-auto p-6">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
### shadcn-ui Sidebar (collapsible)
|
||||
```tsx
|
||||
'use client';
|
||||
import { Sidebar, SidebarContent, SidebarMenu, SidebarMenuItem } from '@/components/ui/sidebar';
|
||||
import { Home, Users, Settings } from 'lucide-react';
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
const items = [
|
||||
{ title: 'Home', url: '/', icon: Home },
|
||||
{ title: 'Users', url: '/users', icon: Users },
|
||||
{ title: 'Settings', url: '/settings', icon: Settings },
|
||||
];
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
export function AppSidebar() {
|
||||
return (
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<a href={item.url}>
|
||||
<item.icon className="size-4" />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
### Stat Card Grid
|
||||
```tsx
|
||||
function StatCards({ stats }: { stats: Stat[] }) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{stats.map((s) => (
|
||||
<div key={s.id} className="rounded-lg border bg-card p-6">
|
||||
<div className="text-sm text-muted-foreground">{s.label}</div>
|
||||
<div className="mt-2 text-3xl font-semibold">{s.value}</div>
|
||||
<div className="mt-1 text-sm text-emerald-600">{s.delta}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
### TanStack Table (server pagination)
|
||||
```tsx
|
||||
'use client';
|
||||
import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
export function UsersTable() {
|
||||
const [page, setPage] = useState(0);
|
||||
const { data } = useQuery({
|
||||
queryKey: ['users', page],
|
||||
queryFn: () => fetch(`/api/users?page=${page}`).then(r => r.json()),
|
||||
});
|
||||
|
||||
const table = useReactTable({
|
||||
data: data?.users ?? [],
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: data?.totalPages,
|
||||
});
|
||||
|
||||
return <table>...</table>;
|
||||
}
|
||||
```
|
||||
|
||||
### Ecommerce PLP (RSC + filter)
|
||||
```tsx
|
||||
// app/products/page.tsx
|
||||
export default async function ProductsPage({ searchParams }: { searchParams: Promise<{ category?: string; sort?: string }> }) {
|
||||
const params = await searchParams;
|
||||
const products = await getProducts({ category: params.category, sort: params.sort });
|
||||
return (
|
||||
<div className="grid grid-cols-[240px_1fr] gap-6">
|
||||
<FilterSidebar />
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
|
||||
{products.map(p => <ProductCard key={p.id} product={p} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Cart Drawer (Zustand + shadcn Sheet)
|
||||
```tsx
|
||||
'use client';
|
||||
import { create } from 'zustand';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||
|
||||
const useCart = create<{ items: Item[]; add: (i: Item) => void; remove: (id: string) => void }>((set) => ({
|
||||
items: [],
|
||||
add: (i) => set((s) => ({ items: [...s.items, i] })),
|
||||
remove: (id) => set((s) => ({ items: s.items.filter(x => x.id !== id) })),
|
||||
}));
|
||||
|
||||
export function CartDrawer() {
|
||||
const items = useCart((s) => s.items);
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger>Cart ({items.length})</SheetTrigger>
|
||||
<SheetContent>
|
||||
{items.map(i => <div key={i.id}>{i.name}</div>)}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Server Action (Add to cart)
|
||||
```tsx
|
||||
// app/actions/cart.ts
|
||||
'use server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export async function addToCart(productId: string, qty: number) {
|
||||
const cart = (await cookies()).get('cart');
|
||||
// ... persist
|
||||
return { success: true };
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| New SaaS dashboard | Next.js 16 + shadcn + Tailwind v4 |
|
||||
| Ecommerce | Next.js Commerce 또는 Shopify Hydrogen |
|
||||
| Internal admin | Refine.dev / Retool |
|
||||
| Mobile-first ecommerce | Astro + Solid Islands |
|
||||
| Real-time dashboard | Next.js + WebSocket (Pusher/Ably) |
|
||||
| Heavy data viz | Tremor + Recharts |
|
||||
|
||||
**기본값**: 매 SaaS 매 Next.js + shadcn-ui + Tailwind v4. 매 Ecommerce 매 Next.js Commerce 템플릿 fork.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Frontend_Architecture]] · [[Layout_Pattern]]
|
||||
- 변형: [[Admin_Dashboard]] · [[PLP_PDP]] · [[Cart_Checkout]]
|
||||
- 응용: [[Linear_App]] · [[Shopify_Hydrogen]] · [[Vercel_Commerce]]
|
||||
- Adjacent: [[Next.js]] · [[Tailwind_v4]] · [[shadcn-ui]] · [[TanStack_Query]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: greenfield SaaS / ecommerce 의 layout shell 구축, design system 적용.
|
||||
**언제 X**: 매 micro-frontend / heavy SSR-disabled 의 SPA — 매 다른 stack (Vite + React Router) 적합.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **모든 component 'use client'**: 매 RSC 의 benefit 상실 — server-first.
|
||||
- **inline style + Tailwind 혼재**: 매 inconsistency — design token 통일.
|
||||
- **Custom UI library scratch**: 매 shadcn-ui copy-paste 가 maintenance 우위.
|
||||
- **Cart in localStorage only**: 매 cross-device sync 불가 — server-side cart + cookie.
|
||||
- **No skeleton during loading**: 매 CLS / UX 저하 — Suspense + skeleton.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Next.js 16 docs, shadcn-ui v2, Tailwind v4, Vercel Commerce).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Next.js 16 / shadcn / Tailwind v4 modern stack |
|
||||
|
||||
Reference in New Issue
Block a user