# Frontend Project Templates Complete scaffolds for web applications across frameworks and rendering strategies. ## Next.js 14+ (App Router) ### Full Directory Tree ``` my-app/ ├── src/ │ ├── app/ │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── loading.tsx │ │ ├── error.tsx │ │ ├── not-found.tsx │ │ ├── globals.css │ │ ├── (auth)/ │ │ │ ├── layout.tsx │ │ │ ├── login/ │ │ │ │ └── page.tsx │ │ │ └── register/ │ │ │ └── page.tsx │ │ ├── dashboard/ │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── loading.tsx │ │ │ └── settings/ │ │ │ └── page.tsx │ │ └── api/ │ │ └── health/ │ │ └── route.ts │ ├── components/ │ │ ├── ui/ │ │ │ ├── button.tsx │ │ │ ├── input.tsx │ │ │ └── card.tsx │ │ ├── features/ │ │ │ ├── header.tsx │ │ │ ├── sidebar.tsx │ │ │ └── user-menu.tsx │ │ └── providers.tsx │ ├── lib/ │ │ ├── db.ts │ │ ├── auth.ts │ │ ├── utils.ts │ │ └── validations.ts │ ├── hooks/ │ │ └── use-debounce.ts │ └── types/ │ └── index.ts ├── public/ │ ├── favicon.ico │ └── images/ ├── tests/ │ ├── setup.ts │ ├── components/ │ │ └── button.test.tsx │ └── e2e/ │ └── home.spec.ts ├── next.config.ts ├── tailwind.config.ts ├── postcss.config.js ├── tsconfig.json ├── package.json ├── .env.local.example ├── .eslintrc.json └── .gitignore ``` ### src/app/layout.tsx ```tsx import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; import { Providers } from '@/components/providers'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { title: { default: 'My App', template: '%s | My App', }, description: 'A Next.js application', }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ### src/app/page.tsx ```tsx export default function HomePage() { return (

Welcome

); } ``` ### src/app/loading.tsx ```tsx export default function Loading() { return (
); } ``` ### src/app/error.tsx ```tsx 'use client'; import { useEffect } from 'react'; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { useEffect(() => { console.error(error); }, [error]); return (

Something went wrong

); } ``` ### src/app/not-found.tsx ```tsx import Link from 'next/link'; export default function NotFound() { return (

Not Found

Could not find the requested resource.

Return Home
); } ``` ### src/app/api/health/route.ts ```typescript import { NextResponse } from 'next/server'; export async function GET() { return NextResponse.json({ status: 'healthy', timestamp: new Date().toISOString() }); } ``` ### src/components/providers.tsx ```tsx 'use client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useState } from 'react'; export function Providers({ children }: { children: React.ReactNode }) { const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, refetchOnWindowFocus: false, }, }, }) ); return ( {children} ); } ``` ### src/lib/utils.ts ```typescript import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } ``` ### next.config.ts ```typescript import type { NextConfig } from 'next'; const nextConfig: NextConfig = { experimental: { typedRoutes: true, }, images: { remotePatterns: [ { protocol: 'https', hostname: '**.example.com', }, ], }, }; export default nextConfig; ``` ### tailwind.config.ts ```typescript import type { Config } from 'tailwindcss'; const config: Config = { content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], theme: { extend: { colors: { border: 'hsl(var(--border))', background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))', primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', }, }, }, }, plugins: [], }; export default config; ``` ### package.json (Key Dependencies) ```json { "name": "my-app", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint", "test": "vitest", "test:e2e": "playwright test" }, "dependencies": { "next": "^14.2.0", "react": "^18.3.0", "react-dom": "^18.3.0", "@tanstack/react-query": "^5.0.0", "clsx": "^2.1.0", "tailwind-merge": "^2.2.0", "zod": "^3.22.0" }, "devDependencies": { "@types/node": "^20.0.0", "@types/react": "^18.3.0", "typescript": "^5.4.0", "tailwindcss": "^3.4.0", "postcss": "^8.4.0", "autoprefixer": "^10.4.0", "eslint": "^8.57.0", "eslint-config-next": "^14.2.0", "vitest": "^1.6.0", "@testing-library/react": "^15.0.0", "@vitejs/plugin-react": "^4.2.0", "@playwright/test": "^1.43.0" } } ``` ### .env.local.example ```env # Database DATABASE_URL=postgresql://user:password@localhost:5432/mydb # Auth NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=change-me # External APIs NEXT_PUBLIC_API_URL=http://localhost:8000 ``` ### Test Setup (Vitest) ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import { resolve } from 'path'; export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', setupFiles: './tests/setup.ts', css: true, }, resolve: { alias: { '@': resolve(__dirname, './src'), }, }, }); ``` ```typescript // tests/setup.ts import '@testing-library/jest-dom/vitest'; ``` ```tsx // tests/components/button.test.tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi } from 'vitest'; // import { Button } from '@/components/ui/button'; describe('Button', () => { it('renders with text', () => { // render(); // expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument(); }); it('calls onClick handler', async () => { const onClick = vi.fn(); // render(); // await userEvent.click(screen.getByRole('button')); // expect(onClick).toHaveBeenCalledOnce(); }); }); ``` --- ## Nuxt 3 ### Full Directory Tree ``` my-app/ ├── app.vue ├── nuxt.config.ts ├── pages/ │ ├── index.vue │ ├── login.vue │ └── dashboard/ │ ├── index.vue │ └── settings.vue ├── components/ │ ├── ui/ │ │ ├── AppButton.vue │ │ └── AppCard.vue │ ├── AppHeader.vue │ └── AppSidebar.vue ├── composables/ │ ├── useAuth.ts │ └── useApi.ts ├── server/ │ ├── api/ │ │ ├── health.get.ts │ │ └── users/ │ │ ├── index.get.ts │ │ ├── index.post.ts │ │ └── [id].get.ts │ ├── middleware/ │ │ └── auth.ts │ └── utils/ │ └── db.ts ├── stores/ │ └── auth.ts ├── layouts/ │ ├── default.vue │ └── auth.vue ├── middleware/ │ └── auth.ts ├── plugins/ │ └── api.ts ├── assets/ │ └── css/ │ └── main.css ├── public/ │ └── favicon.ico ├── tests/ │ └── components/ │ └── AppButton.test.ts ├── tailwind.config.ts ├── tsconfig.json ├── package.json ├── .env.example └── .gitignore ``` ### nuxt.config.ts ```typescript export default defineNuxtConfig({ devtools: { enabled: true }, modules: [ '@nuxtjs/tailwindcss', '@pinia/nuxt', '@vueuse/nuxt', ], css: ['~/assets/css/main.css'], runtimeConfig: { databaseUrl: process.env.DATABASE_URL || '', jwtSecret: process.env.JWT_SECRET || '', public: { apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api', }, }, typescript: { strict: true, typeCheck: true, }, compatibilityDate: '2024-04-01', }); ``` ### app.vue ```vue ``` ### pages/index.vue ```vue ``` ### composables/useApi.ts ```typescript export function useApi() { const config = useRuntimeConfig(); async function $fetch(url: string, options?: RequestInit): Promise { const response = await fetch(`${config.public.apiBase}${url}`, { headers: { 'Content-Type': 'application/json', ...options?.headers }, ...options, }); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); } return { $fetch }; } ``` ### stores/auth.ts (Pinia) ```typescript import { defineStore } from 'pinia'; interface User { id: string; email: string; name: string; } export const useAuthStore = defineStore('auth', () => { const user = ref(null); const isAuthenticated = computed(() => !!user.value); async function login(email: string, password: string) { const { $fetch } = useApi(); const data = await $fetch<{ user: User; token: string }>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), }); user.value = data.user; } function logout() { user.value = null; navigateTo('/login'); } return { user, isAuthenticated, login, logout }; }); ``` ### server/api/health.get.ts ```typescript export default defineEventHandler(() => { return { status: 'healthy', timestamp: new Date().toISOString() }; }); ``` ### server/api/users/index.get.ts ```typescript export default defineEventHandler(async (event) => { const query = getQuery(event); const skip = Number(query.skip) || 0; const limit = Number(query.limit) || 20; // Replace with actual database query return { items: [], total: 0 }; }); ``` --- ## Astro ### Full Directory Tree ``` my-site/ ├── src/ │ ├── layouts/ │ │ ├── BaseLayout.astro │ │ └── PostLayout.astro │ ├── pages/ │ │ ├── index.astro │ │ ├── about.astro │ │ ├── blog/ │ │ │ ├── index.astro │ │ │ └── [...slug].astro │ │ └── api/ │ │ └── health.ts │ ├── components/ │ │ ├── Header.astro │ │ ├── Footer.astro │ │ ├── Card.astro │ │ └── react/ │ │ └── Counter.tsx │ ├── content/ │ │ ├── config.ts │ │ └── blog/ │ │ ├── first-post.md │ │ └── second-post.md │ ├── styles/ │ │ └── global.css │ └── env.d.ts ├── public/ │ ├── favicon.svg │ └── images/ ├── astro.config.mjs ├── tailwind.config.mjs ├── tsconfig.json ├── package.json ├── .gitignore └── .env.example ``` ### astro.config.mjs ```javascript import { defineConfig } from 'astro/config'; import tailwind from '@astrojs/tailwind'; import react from '@astrojs/react'; import mdx from '@astrojs/mdx'; import sitemap from '@astrojs/sitemap'; export default defineConfig({ site: 'https://example.com', integrations: [tailwind(), react(), mdx(), sitemap()], output: 'static', // or 'server' for SSR, 'hybrid' for mixed }); ``` ### src/layouts/BaseLayout.astro ```astro --- import Header from '../components/Header.astro'; import Footer from '../components/Footer.astro'; import '../styles/global.css'; interface Props { title: string; description?: string; } const { title, description = 'My Astro site' } = Astro.props; --- {title}