sadmann7/skateshop
An open source e-commerce skateshop build with everything new in Next.js.
Multi-tenant e-commerce platform where users create skateboard shops with inventory management
Users register and authenticate through Clerk, then create stores with configurable subscription plans. Store owners add products with images uploaded via UploadThing, categorize inventory, and manage orders through the dashboard. Customer-facing storefronts display products filtered by category and availability, while the admin interface handles inventory updates, plan limit enforcement, and Stripe payment processing.
Under the hood, the system uses 2 feedback loops, 3 data pools, 3 control points to manage its runtime behavior.
A 6-component fullstack. 314 files analyzed. Data flows through 6 distinct pipeline stages.
How Data Flows Through the System
Users register and authenticate through Clerk, then create stores with configurable subscription plans. Store owners add products with images uploaded via UploadThing, categorize inventory, and manage orders through the dashboard. Customer-facing storefronts display products filtered by category and availability, while the admin interface handles inventory updates, plan limit enforcement, and Stripe payment processing.
- User Authentication — Clerk handles user registration, login, and session management, providing user objects with unique IDs that link to store ownership
- Store Registration — CreateStoreDialog component validates against plan limits, generates unique store IDs using generateId(), and inserts new Store records with default free plan settings [User → Store]
- Product Creation — Dashboard forms collect product details, validate against store limits, upload images via UploadThing to get StoredFile objects, then insert Product records with decimal pricing and integer inventory [Store → Product]
- Image Upload Processing — UploadThing middleware handles file validation and CDN upload, returning StoredFile objects with permanent URLs that get stored as JSON in product images columns [File Upload → StoredFile]
- Storefront Display — Server components query products by store ID and status, filter by categories and subcategories, and render product cards with images from StoredFile URLs [Product → Product Display]
- Dashboard Navigation — DashboardSidebar reads current route segments, matches against NavItem definitions, and renders active states while StoreSwitcher manages multi-store context switching [NavItem → Navigation UI]
Data Models
The data structures that flow between stages — the contracts that hold the system together.
src/db/schema/stores.tstable with id: varchar(30), userId: varchar(36), slug: text, name: text, plan: storePlanEnum('free'|'standard'|'pro'), planEndsAt: timestamp, productLimit: integer, stripeAccountId: varchar
Created when user registers a new store, updated when plan changes, referenced by all store-related operations
src/db/schema/products.tstable with id: varchar(30), name: text, description: text, images: json(StoredFile[]), categoryId: varchar(30), price: decimal(10,2), inventory: integer, status: productStatusEnum('active'|'draft'|'archived'), storeId: varchar(30)
Created by store owners, updated through dashboard, displayed on storefront, archived when discontinued
src/types/index.tsinterface with id: string, name: string, url: string extending ClientUploadedFileData
Created during file upload via UploadThing, stored as JSON in product images, served from CDN URLs
src/types/index.tsinterface with title: string, href?: string, active?: boolean, disabled?: boolean, external?: boolean, icon?: keyof Icons, label?: string, description?: string
Defined statically in components, dynamically constructed based on user permissions and current route
src/types/index.tsinterface with id: Store['plan'], title: string, description: string, features: string[], stripePriceId: string, limits: {stores: number, products: number, tags: number, variants: number}
Defined as configuration, enforced during store operations, updated when user changes subscription
Hidden Assumptions
Things this code relies on but never validates. These are the things that cause silent failures when the system changes.
DATABASE_URL environment variable contains a valid PostgreSQL connection string that stays valid for the application lifetime
If this fails: If the connection string becomes invalid (credentials expire, server moves, network changes), all database operations fail with connection errors but the application continues running with broken state
src/db/index.ts:postgres
Route segments array from useSelectedLayoutSegments() has predictable structure where segments[0] is the primary section and segments.includes() accurately reflects nested routes
If this fails: If Next.js changes segment ordering or includes unexpected segments (like dynamic route parameters), navigation highlights wrong sections or shows multiple active states simultaneously
src/app/(dashboard)/store/[storeId]/_components/dashboard-sidebar.tsx:useSelectedLayoutSegments
useSidebar hook is only called within components wrapped by SidebarProvider in the component tree
If this fails: Calling useSidebar outside the provider throws runtime error 'useSidebar must be used within a SidebarProvider', crashing the component that tries to access sidebar state
src/components/layouts/sidebar-provider.tsx:useSidebar
StoredFile.url contains a permanently accessible CDN URL that will remain valid indefinitely without authentication or expiration
If this fails: If UploadThing CDN URLs expire, change domains, or require authentication, product images break silently - users see broken image placeholders but no error is thrown
src/types/index.ts:StoredFile
storesPromise and planMetricsPromise are actual Promise objects that will resolve successfully with expected data shapes
If this fails: If promises reject or resolve with unexpected data, React.use() throws unhandled errors that crash the component, making store switching completely unusable
src/app/(dashboard)/store/[storeId]/_components/store-switcher.tsx:React.use
nanoid with 30-character length provides sufficient uniqueness for all database primary keys across all stores and products without collisions
If this fails: ID collisions cause database constraint violations during insert operations, failing store or product creation with cryptic errors that don't indicate the root cause
src/lib/id.ts:generateId
Database server clock and application server clock are synchronized when $onUpdate() executes new Date() versus database current_timestamp default
If this fails: Clock skew causes updatedAt timestamps to be inconsistent - some records show future dates, others lag behind, breaking audit trails and temporal queries
src/db/schema/utils.ts:updatedAt
storeId parameter from URL route [storeId] is always a valid store identifier that matches existing store records in the database
If this fails: Invalid storeId causes selectedStore to be undefined, leading to component render errors when trying to access store properties, making dashboard inaccessible
src/app/(dashboard)/store/[storeId]/_components/store-switcher.tsx:useParams
stripePriceId corresponds to actual Stripe price objects that exist in the connected Stripe account and remain valid
If this fails: Invalid Stripe price IDs cause subscription upgrades to fail with Stripe API errors, but users see generic error messages without understanding the payment issue
src/types/index.ts:Plan
JSON-stored StoredFile arrays in product.images column always contain objects with id, name, and url properties
If this fails: Corrupted JSON or missing properties cause runtime errors when components try to render product images, showing TypeScript errors in production instead of fallback images
src/types/index.ts:StoredFile
System Behavior
How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.
Data Pools
Primary data store containing all relational tables for stores, products, users, orders, and metadata with foreign key constraints
React Context holding boolean open/close state for mobile sidebar across dashboard pages
CDN-backed file storage for product images with permanent URLs referenced in database JSON fields
Feedback Loops
- Plan Limit Enforcement (circuit-breaker, balancing) — Trigger: User attempts to create store or product. Action: StoreSwitcher checks current usage against plan limits from getUserPlanMetrics. Exit: Allow action if under limits, show upgrade dialog if exceeded.
- Active Route Highlighting (polling, reinforcing) — Trigger: Navigation occurs in dashboard. Action: useSelectedLayoutSegments hook tracks current route and updates NavItem active states. Exit: Continuous tracking while user navigates.
Delays
- File Upload Processing (async-processing, ~variable) — Product creation waits for image uploads to complete and return StoredFile URLs before database insert
- Database Query Latency (async-processing, ~milliseconds) — All store and product operations wait for PostgreSQL responses through Drizzle ORM connection
Control Points
- Store Plan Limits (threshold) — Controls: Maximum number of stores, products, tags, and variants per subscription plan. Default: Plan-dependent: free/standard/pro tiers
- Database Connection String (env-var) — Controls: Which PostgreSQL database instance the application connects to. Default: env.DATABASE_URL
- Stripe Integration (env-var) — Controls: Payment processing and subscription management configuration. Default: Stripe API keys from environment
Technology Stack
App Router framework providing server components, file-based routing, and API routes for the e-commerce application
Type-safe database ORM that generates TypeScript types from schema definitions and handles PostgreSQL queries
Authentication provider handling user registration, login, session management, and user profile data
Payment processing and subscription management for store plans and customer transactions
File upload service that handles product image storage and returns CDN URLs for database storage
Utility-first CSS framework for styling components with responsive design and dark mode support
React component library built on Radix UI primitives providing accessible and customizable UI components
Key Components
- db (gateway) — Creates and exports the Drizzle database client connection using PostgreSQL connection string from environment
src/db/index.ts - DashboardSidebar (orchestrator) — Renders the main navigation sidebar for store dashboard with dynamic route segments and active state management
src/app/(dashboard)/store/[storeId]/_components/dashboard-sidebar.tsx - StoreSwitcher (dispatcher) — Manages switching between multiple stores for a user, handles rate limiting validation and store creation workflow
src/app/(dashboard)/store/[storeId]/_components/store-switcher.tsx - SidebarProvider (registry) — React Context provider that manages global sidebar open/close state across the application
src/components/layouts/sidebar-provider.tsx - generateId (factory) — Generates unique identifiers using nanoid for database primary keys with consistent 30-character length
src/lib/id.ts - lifecycleDates (factory) — Provides standard createdAt and updatedAt timestamp columns for all database tables with automatic updates
src/db/schema/utils.ts
Explore the interactive analysis
See the full architecture map, data flow, and code patterns visualization.
Analyze on CodeSeaRelated Fullstack Repositories
Frequently Asked Questions
What is skateshop used for?
Multi-tenant e-commerce platform where users create skateboard shops with inventory management sadmann7/skateshop is a 6-component fullstack written in TypeScript. Data flows through 6 distinct pipeline stages. The codebase contains 314 files.
How is skateshop architected?
skateshop is organized into 4 architecture layers: Database Schema Layer, Data Access Layer, UI Component Layer, Route Handler Layer. Data flows through 6 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.
How does data flow through skateshop?
Data moves through 6 stages: User Authentication → Store Registration → Product Creation → Image Upload Processing → Storefront Display → .... Users register and authenticate through Clerk, then create stores with configurable subscription plans. Store owners add products with images uploaded via UploadThing, categorize inventory, and manage orders through the dashboard. Customer-facing storefronts display products filtered by category and availability, while the admin interface handles inventory updates, plan limit enforcement, and Stripe payment processing. This pipeline design reflects a complex multi-stage processing system.
What technologies does skateshop use?
The core stack includes Next.js 14 (App Router framework providing server components, file-based routing, and API routes for the e-commerce application), Drizzle ORM (Type-safe database ORM that generates TypeScript types from schema definitions and handles PostgreSQL queries), Clerk (Authentication provider handling user registration, login, session management, and user profile data), Stripe (Payment processing and subscription management for store plans and customer transactions), UploadThing (File upload service that handles product image storage and returns CDN URLs for database storage), Tailwind CSS (Utility-first CSS framework for styling components with responsive design and dark mode support), and 1 more. A focused set of dependencies that keeps the build manageable.
What system dynamics does skateshop have?
skateshop exhibits 3 data pools (PostgreSQL Database, Sidebar Context State), 2 feedback loops, 3 control points, 2 delays. The feedback loops handle circuit-breaker and polling. These runtime behaviors shape how the system responds to load, failures, and configuration changes.
What design patterns does skateshop use?
4 design patterns detected: Multi-tenant SaaS Architecture, Server Components with Promises, Schema-First Database Design, Compound Component Pattern.
Analyzed on April 20, 2026 by CodeSea. Written by Karolina Sarna.