Overview
Frontend tech stack, key decisions, and architectural patterns
Frontend Overview
The frontend is a Nuxt 3 application providing a marketing landing page, authentication flows, Stripe checkout, and a user dashboard — all styled with a dark theme using Tailwind CSS.
Key Decisions
Nuxt 3
Nuxt 3 provides server-side rendering (SSR) for SEO, auto-imports for composables and components, file-based routing, and a mature plugin ecosystem. The app uses SSR for the public landing page and client-side rendering for authenticated views.
Vue 3 Composition API
All components use the <script setup> syntax with TypeScript. This provides type safety, better IDE support, and cleaner composable patterns.
Tailwind CSS
Utility-first CSS with a custom dark theme. No component library — all UI components are built from scratch to keep the bundle small and the design fully customizable.
Pinia
Lightweight state management for auth state (token, user profile). Only one store (useAuthStore) since most state is managed by composables.
TypeScript
Full TypeScript coverage with interfaces for all API request/response shapes. Types are defined in types/ and auto-imported.
Design System
The app uses a consistent dark theme:
| Token | Value | Usage |
|---|---|---|
| background | #0a0a0b | Page background |
| foreground | #fafafa | Primary text |
| accent | #10b981 | Links, buttons, active states |
| card | #111113 | Card backgrounds |
| border | #27272a | Borders, dividers |
| muted | #1a1a1c | Secondary backgrounds |
| muted-fg | #a1a1aa | Secondary text |
Fonts
- Geist — sans-serif body text
- JetBrains Mono — monospace for code
Architectural Patterns
Dual-Mode Landing Page
pages/index.vue renders the marketing landing page for guests and the authenticated dashboard for logged-in users, using a simple v-if/v-else on authStore.isAuthenticated.
Component-Based Layouts
Instead of Nuxt's layouts/ directory, layouts are implemented as components (AuthenticatedLayout.vue) that wrap page content. This gives more control over layout composition.
Auto-Import
Nuxt auto-imports all composables from composables/ and all components from components/. The pathPrefix: false option means components are referenced by their filename only (e.g., <AppButton> not <UiAppButton>).
Composable Pattern
Each feature has a dedicated composable (e.g., useAuth, useCheckout, useSubscription) that encapsulates API calls, loading states, and error handling. Composables return readonly refs for reactivity.
API Communication
The frontend communicates with the backend exclusively through REST API calls. All requests go through the useApi composable, which handles authentication headers and token refresh automatically.
The useApi Composable
const api = useApi()
// GET request (authenticated)
const profile = await api('/api/v1/user/profile')
// POST request
const result = await api('/api/v1/auth/login', {
method: 'POST',
body: { email, password }
})
What it does:
- Base URL injection — Prepends
NUXT_PUBLIC_API_BASE_URLto all requests - Auth headers — Automatically adds
Authorization: Bearer <token>when a token exists - 401 retry — On a 401 response, attempts to refresh the token and retry the request once
- Logout on failure — If the refresh fails, clears the auth store (logs the user out)
Request Flow
Frontend Request
│
├── Add Authorization header (if token exists)
│
▼
Backend API
│
├── 200/201 → Return data
│
├── 401 → Refresh token
│ │
│ ├── Success → Retry original request
│ │
│ └── Failure → Logout user
│
└── Other error → Return error to caller
API Endpoints
| Frontend Feature | Backend Endpoint | Method |
|---|---|---|
| Register | /api/v1/auth/register | POST |
| Login | /api/v1/auth/login | POST |
| Social Login | /api/v1/auth/social | POST |
| Logout | /api/v1/auth/logout | POST |
| Token Refresh | /api/v1/auth/token/refresh | POST |
| Forgot Password | /api/v1/auth/forgot-password | POST |
| Reset Password | /api/v1/auth/reset-password | POST |
| User Profile | /api/v1/user/profile | GET |
| Change Password | /api/v1/user/change-password | POST |
| Pricing Plans | /api/v1/plans | GET |
| Checkout Session | /api/v1/subscription/checkout | POST |
| Subscription Status | /api/v1/subscription | GET |
CORS Configuration
The backend must allow requests from the frontend origin. Set CORS_ALLOW_ORIGIN in the backend's .env.local:
# Local development
CORS_ALLOW_ORIGIN=^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$
# Production
CORS_ALLOW_ORIGIN=^https://yourdomain\.com$
Error Handling
Each composable handles API errors with a consistent pattern:
const { error, isLoading } = useAuth()
// error is a Ref<string | null>
// isLoading is a Ref<boolean>
Errors from the backend are parsed from the response JSON ({ "error": "message" }) and exposed as reactive refs that components can display.