A modern, production-ready Nuxt 4 boilerplate with authentication, dashboard navigation, and a clean dark theme design system.
π³ Docker Ready: Full Docker support with PostgreSQL and Adminer. See DOCKER.md for complete guide.
- Dark Theme: Professional dark color scheme with purple gradient accents
- Responsive: Mobile-first design that works on all screen sizes
- Customizable: Centralized theme configuration in
/config/theme.ts - Smooth Animations: Polished micro-interactions and transitions
- JWT-based: Secure authentication with HTTP-only cookies
- Protected Routes: Middleware-based route protection
- Login/Register: Pre-built authentication pages
- User Management: Profile and logout functionality
- Public Navbar: Clean marketing navbar with mobile drawer
- Dashboard Sidebar: Professional sidebar with nested menu support
- Config-Driven: Easy to add/remove menu items via configuration
- Auto-Active States: Highlights current route and auto-expands parent items
- Mobile Optimized: Drawer pattern for mobile navigation
- Nuxt 4: Latest Nuxt with all modern features
- Vue 3: Composition API with
<script setup> - TypeScript: Fully typed codebase
- Nuxt UI: Beautiful UI components
- Drizzle ORM: Type-safe database queries
- PostgreSQL: Production-ready database
- Tailwind CSS: Utility-first styling
- Modular Components: Reusable UI components in
/components/ui/ - Layout Components: Navigation and layout components
- Auth Components: Authentication-specific components
- Demo Pages: Example dashboard pages with nested routes
- Node.js 22+
- pnpm (recommended) or npm
- Docker & Docker Compose (for Docker setup)
- Clone the repository
git clone <your-repo-url>
cd nuxt-boilerplate- Install dependencies
pnpm install- Set up environment variables
cp .env.example .envEdit .env and configure:
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/nuxt_boilerplate"
JWT_SECRET="your-super-secret-jwt-key-change-this"- Start PostgreSQL with Docker
docker compose up postgres -d- Run database migrations
pnpm db:push- Start development server
pnpm devOpen http://localhost:3000 in your browser.
Run the entire application stack (App + PostgreSQL + Adminer) with Docker:
- Clone and configure
git clone <your-repo-url>
cd nuxt-boilerplate
cp .env.example .env- Build and start all services
docker compose up --buildThis will start:
- App: http://localhost:3000
- Adminer (Database UI): http://localhost:8080
- PostgreSQL: localhost:5433
- Access Adminer Open http://localhost:8080 and login with:
- System: PostgreSQL
- Server: postgres
- Username: postgres
- Password: postgres
- Database: nuxt_boilerplate
- Stop services
docker compose down- Stop and remove volumes (clears database)
docker compose down -v# Start only database and Adminer
docker compose up postgres adminer -d
# Start only the app (requires DB running)
docker compose up app
# View logs
docker compose logs -f app
# Rebuild app after code changes
docker compose up --build app
# Execute commands in running container
docker compose exec app pnpm db:push
docker compose exec app shnuxt-boilerplate/
βββ components/
β βββ layout/ # Navigation components
β β βββ AppNavbar.vue
β β βββ DashboardSidebar.vue
β β βββ DashboardMenuItem.vue
β β βββ MobileDrawer.vue
β βββ ui/ # Reusable UI components
β β βββ UiButton.vue
β β βββ UiCard.vue
β β βββ UiInput.vue
β βββ auth/ # Auth-specific components
β βββ AuthWrapper.vue
β βββ AuthPanelContent.vue
β βββ AuthFormHeader.vue
β
βββ pages/
β βββ index.vue # Landing page
β βββ login.vue # Login page
β βββ register.vue # Register page
β βββ dashboard/ # Dashboard pages
β βββ index.vue
β βββ settings.vue
β βββ analytics/
β βββ users/
β
βββ server/
β βββ api/ # API routes
β β βββ auth/
β β βββ user/
β βββ middleware/ # Server middleware
β βββ utils/ # Server utilities
β
βββ config/
β βββ navigation.ts # Navigation configuration
β βββ theme.ts # Theme configuration
β βββ NAVIGATION.md # Navigation docs
β
βββ composables/
β βββ useAuth.ts # Auth composable
β
βββ middleware/
β βββ auth.ts # Auth middleware
β
βββ db/
β βββ index.ts # Database connection
β βββ schema.ts # Database schema
β
βββ app.vue # Root component
Edit /config/theme.ts to customize the light/dark tokens that power the global CSS variables:
export const themeConfig = {
storageKey: 'app-theme',
cookieName: 'app-theme',
themes: {
light: {
page: '#f6f8fb',
surface: '#ffffff',
text: '#0f172a',
accent: '#6366f1',
// ...extend tokens as needed
},
dark: {
page: '#050a14',
surface: '#0f172a',
text: '#e5e7eb',
accent: '#818cf8',
// ...extend tokens as needed
}
}
}- Toggle: Drop
<ThemeToggle />anywhere (already wired into the public navbar and dashboard sidebar). Addwith-labelto show text. - Composable:
const { theme, isDark, toggleTheme, setTheme } = useTheme()for programmatic control. - Persistence & defaults: Preference is stored in both
localStorageand aapp-themecookie; first visit falls back toprefers-color-schemewith an inline pre-paint script to avoid flashes. - Classes & variables: The active theme class (
light/dark) lives on<html>and drives the CSS variables like--bg-page,--surface,--text-primary,--accent, etc. Use these in component styles for theme-safe colors. - Extending tokens: Add new keys under
themeConfig.themes.light/darkand consume them via CSS variables or by extending the variable list inplugins/theme.tsif you introduce new tokens.
Edit /config/navigation.ts to add/remove menu items:
// Public navbar links
export const publicNavLinks: NavLink[] = [
{ label: 'Features', to: '/#features' },
{ label: 'Pricing', to: '/#pricing' },
]
// Dashboard sidebar menu
export const dashboardMenuItems: DashboardMenuItem[] = [
{
label: 'Analytics',
icon: 'i-heroicons-chart-bar',
children: [
{ label: 'Reports', to: '/dashboard/analytics/reports', icon: 'i-heroicons-document-text' },
],
},
]- Create the page file
touch pages/dashboard/your-page.vue- Add to navigation config
// In config/navigation.ts
{
label: 'Your Page',
to: '/dashboard/your-page',
icon: 'i-heroicons-sparkles',
}That's it! The navigation will automatically include your new page.
const { register } = useAuth()
await register({
email: 'user@example.com',
password: 'securepassword'
})const { login } = useAuth()
await login({
email: 'user@example.com',
password: 'securepassword'
})Add middleware: 'auth' to your page:
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
</script>const { user } = useAuth()
console.log(user.value?.email)The database schema is defined in /db/schema.ts using Drizzle ORM:
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
password: varchar('password', { length: 255 }).notNull(),
createdAt: timestamp('created_at').defaultNow(),
})Push schema changes to database:
pnpm db:pushGenerate migrations:
pnpm db:generate-
Mobile: < 768px
- Drawer navigation
- Stacked layouts
-
Tablet: 768px - 1023px
- Full navbar
- Drawer for dashboard
-
Desktop: β₯ 1024px
- Full navbar
- Fixed sidebar for dashboard
Both public and dashboard layouts use a professional drawer pattern on mobile:
- Smooth slide-in animation
- Dark backdrop with blur
- Close triggers: X button, backdrop click, escape key, route change
- Body scroll prevention
pnpm buildpnpm preview- Set production environment variables
Create a .env.production file:
DATABASE_URL="postgresql://postgres:secure_password@postgres:5432/nuxt_boilerplate"
JWT_SECRET="production-secret-min-32-characters-long"
NODE_ENV="production"- Build and deploy with Docker
# Build the production image
docker compose build app
# Start all services in production mode
docker compose up -d
# Check logs
docker compose logs -f app- Health checks
The containers include health checks for:
- PostgreSQL: Automatic readiness checks
- App: Waits for database to be ready before starting
βββββββββββββββββββββββββββββββββββββββββββ
β β
β Docker Compose Stack β
β β
β ββββββββββββββββββββββββββββββββββββ β
β β Nuxt App (Port 3000) β β
β β - Built with Node 20 Alpine β β
β β - Production optimized β β
β ββββββββββββββββ¬ββββββββββββββββββββ β
β β β
β β connects to β
β β β
β ββββββββββββββββββββββββββββββββββββ β
β β PostgreSQL 16 (Port 5433) β β
β β - Persistent volume β β
β β - Health checks enabled β β
β ββββββββββββββββ¬ββββββββββββββββββββ β
β β β
β β managed by β
β β β
β ββββββββββββββββββββββββββββββββββββ β
β β Adminer (Port 8080) β β
β β - Web-based DB admin β β
β β - No setup required β β
β ββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββ
Make sure to set these in production:
DATABASE_URL="your-production-database-url"
JWT_SECRET="your-production-jwt-secret"
NODE_ENV="production"- π³ Docker Guide: Complete Docker setup and deployment - DOCKER.md
- Navigation System: See
/config/NAVIGATION.md - Navigation Refactoring: See
/NAVIGATION_REFACTOR.md - Theme Configuration: See
/config/README.md
Nuxt auto-imports components with folder prefixes:
components/layout/AppNavbar.vue β <LayoutAppNavbar>
components/ui/UiButton.vue β <UiButton>
components/auth/AuthWrapper.vue β <AuthWrapper>
- Global: Applied to all routes (defined in
nuxt.config.ts) - Named: Applied via
definePageMeta({ middleware: 'auth' }) - Anonymous: Inline functions in
definePageMeta
Reusable reactive logic in /composables/:
useAuth(): Authentication state and methods- Auto-imported throughout the app
# Type checking
pnpm typecheck
# Linting
pnpm lint
# Formatting (if configured)
pnpm formatRecommended extensions:
- Vue - Official
- Tailwind CSS IntelliSense
- ESLint
- TypeScript Vue Plugin (Volar)
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
Built with Nuxt 4 β’ Documentation β’ Nuxt UI