Tools: I Spent 48 Hours Building a Next.js Boilerplate So You Can Ship in 30 Minutes

Tools: I Spent 48 Hours Building a Next.js Boilerplate So You Can Ship in 30 Minutes

Source: Dev.to

The Problem That Kept Me Up at Night ## What I Built ## Why This Boilerplate is Different ## 🌍 Real Internationalization (Not Just a Dictionary) ## πŸ” Role-Based Access Control That Scales ## πŸ”‘ Authentication (NextAuth.js + Google OAuth) ## 🎨 A Design System That Doesn't Suck ## πŸ”§ ESLint That Actually Helps (Not Annoys) ## πŸ“Š SEO Configuration That's Actually Usable ## πŸ§ͺ Testing & CI ## πŸ₯ Health Check ## How to Get Started (The Real Way) ## Step 1: Clone and Install ## Step 2: Configure Your Project ## Step 3: Customize Your Languages (Optional) ## Step 4: Set Up Your Roles ## Step 5: Run It ## Available Scripts ## Step 6: Environment (First-Time Setup) ## Prerequisites ## The Project Structure (Explained for Humans) ## What You Get Out of the Box ## Real Talk: When Should You Use This? ## What I Learned Building This ## Contributing & Support ## Final Thoughts You know that feeling when you start a new Next.js project and spend the first week just setting things up? Authentication, internationalization, role management, SEO configuration... By the time you're done with the boilerplate, you've lost all that initial excitement. What you get in one line: Type-safe i18n with RTL β†’ NextAuth + Google OAuth β†’ RBAC with parallel routes β†’ SEO (sitemap, robots, manifest) β†’ Dark mode β†’ ESLint + Prettier β†’ Vitest + Playwright β†’ shadcn/ui β†’ One config file. Production-ready. I've been there. Too many times. After launching my third SaaS project this year, I realized I was copy-pasting the same setup code over and over. So I decided to do something about it. Meet my production-ready Next.js boilerplate - not just another "hello world" starter template, but a fully-featured foundation that handles all the boring stuff so you can focus on building your actual product. πŸ”— Live Demo | πŸ“¦ GitHub Repo | πŸš€ Use this template | Deploy on Vercel I'm talking about type-safe translations that catch errors at compile time. No more broken translations in production because you typo'd a key. Here's what makes it special: Most tutorials show you basic auth and call it a day. But what about when you need different dashboards for users and admins? Or when you want to add a "Moderator" role later? I used Next.js 15's parallel routes to make this painless: The layout automatically shows the right dashboard based on the user's role. No messy conditionals scattered everywhere. Want to add a new role? Just create a new parallel route folder. That's it. Auth is built in with NextAuth.js. You get: Copy .env.example to .env, add your secrets, and you're done. I'm using shadcn/ui because: Plus next-themes for light/dark mode with system preference detection and a manual toggle. Let's be honest - most ESLint configs are either too strict or too loose. I spent time configuring rules that: Prettier is wired up too (Tailwind plugin, format on save in .vscode/settings.json). Run npm run lint and npm run prettier:fix for consistent, clean code. Instead of hardcoding metadata everywhere, I created a single JSON configuration file that handles: Done. SEO handled. The same config drives sitemap (sitemap.ts), robots.txt (robots.ts), and manifest (manifest.ts). GET /api/health returns { status: "ok" } for load balancers and Kubernetes probes. This is where most boilerplates leave you hanging. Not this one. Edit lib/config/app-main-meta-data.json: That's your entire brand configuration. One file. Want to add Spanish? Here's how: Done. Your app now speaks Spanish. The boilerplate comes with User and Admin roles. To add more: Add your pages inside that folder Update app/(protected)/layout.tsx to handle the new role: That's genuinely all you need to do. Open http://localhost:3000 and see your fully-configured app running. Copy .env.example to .env. Set NEXT_PUBLIC_APP_URL if you need to override the site URL (e.g. in production). For Google sign-in: set NEXTAUTH_URL, NEXTAUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, then NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true. Optionally set [email protected] so those emails get the admin role. βœ… Next.js 15 with App Router and Server Components βœ… TypeScript (strict mode) βœ… Tailwind CSS βœ… ESLint and Prettier (Next.js, TypeScript, a11y, format on save in .vscode) βœ… NextAuth.js with optional Google OAuth and admin-by-email βœ… i18n with type safety and RTL (en, bn, ar) βœ… RBAC with parallel routes (User / Admin) βœ… SEO from one JSON config (metadata, sitemap, robots, manifest) βœ… next-themes for dark mode (system + manual toggle) βœ… shadcn/ui (accessible, customizable) βœ… Vitest + React Testing Library for unit/component tests βœ… Playwright for E2E in e2e/ βœ… GitHub Actions for lint, format, test, build and E2E βœ… Health check at GET /api/health βœ… Vercel-ready (one-click deploy from the repo) Found a bug? Want to add a feature? The repo is fully open source: πŸ› Report issues ⭐ Star on GitHub 🀝 Submit a PR I built this because I got tired of setting up the same infrastructure for every project. If you're launching a product and don't want to spend two weeks on boilerplate, give it a try. It's saved me probably 30+ hours across my last three projects. Maybe it'll help you too. What's your biggest pain point when starting a new Next.js project? Drop a comment below - I'm always looking to improve this. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: // TypeScript knows all your translation keys t('navigation.home') // βœ… Works t('navigation.homer') // ❌ Compile error - typo caught! Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // TypeScript knows all your translation keys t('navigation.home') // βœ… Works t('navigation.homer') // ❌ Compile error - typo caught! CODE_BLOCK: // TypeScript knows all your translation keys t('navigation.home') // βœ… Works t('navigation.homer') // ❌ Compile error - typo caught! COMMAND_BLOCK: app/ (protected)/ @admin/ # Admin-only views dashboard/ @user/ # User views dashboard/ layout.tsx # Smart routing logic Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: app/ (protected)/ @admin/ # Admin-only views dashboard/ @user/ # User views dashboard/ layout.tsx # Smart routing logic COMMAND_BLOCK: app/ (protected)/ @admin/ # Admin-only views dashboard/ @user/ # User views dashboard/ layout.tsx # Smart routing logic CODE_BLOCK: { "appName": "Your App", "title": "Your Title", "description": "Your Description", "domain": "https://yoursite.com", "keywords": ["your", "keywords"], "social": { "twitter": "@yourhandle" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "appName": "Your App", "title": "Your Title", "description": "Your Description", "domain": "https://yoursite.com", "keywords": ["your", "keywords"], "social": { "twitter": "@yourhandle" } } CODE_BLOCK: { "appName": "Your App", "title": "Your Title", "description": "Your Description", "domain": "https://yoursite.com", "keywords": ["your", "keywords"], "social": { "twitter": "@yourhandle" } } COMMAND_BLOCK: # Grab the code git clone https://github.com/salmanshahriar/nextjs-boilerplate-production-ready.git cd nextjs-boilerplate-production-ready # Install dependencies (use whatever you prefer) npm install # or bun install / yarn install / pnpm install Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: # Grab the code git clone https://github.com/salmanshahriar/nextjs-boilerplate-production-ready.git cd nextjs-boilerplate-production-ready # Install dependencies (use whatever you prefer) npm install # or bun install / yarn install / pnpm install COMMAND_BLOCK: # Grab the code git clone https://github.com/salmanshahriar/nextjs-boilerplate-production-ready.git cd nextjs-boilerplate-production-ready # Install dependencies (use whatever you prefer) npm install # or bun install / yarn install / pnpm install CODE_BLOCK: { "appName": "My Awesome SaaS", "title": "Revolutionary Product That Does X", "description": "We help Y achieve Z", "domain": "https://myawesomesaas.com", "organization": { "name": "My Company", "email": "[email protected]" }, "social": { "twitter": "@myhandle", "github": "https://github.com/myhandle" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "appName": "My Awesome SaaS", "title": "Revolutionary Product That Does X", "description": "We help Y achieve Z", "domain": "https://myawesomesaas.com", "organization": { "name": "My Company", "email": "[email protected]" }, "social": { "twitter": "@myhandle", "github": "https://github.com/myhandle" } } CODE_BLOCK: { "appName": "My Awesome SaaS", "title": "Revolutionary Product That Does X", "description": "We help Y achieve Z", "domain": "https://myawesomesaas.com", "organization": { "name": "My Company", "email": "[email protected]" }, "social": { "twitter": "@myhandle", "github": "https://github.com/myhandle" } } CODE_BLOCK: { "common": { "appName": "Mi App", ... }, "navigation": { "home": "Inicio", "about": "Acerca de" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "common": { "appName": "Mi App", ... }, "navigation": { "home": "Inicio", "about": "Acerca de" } } CODE_BLOCK: { "common": { "appName": "Mi App", ... }, "navigation": { "home": "Inicio", "about": "Acerca de" } } CODE_BLOCK: { "languages": { "supported": ["en", "bn", "ar", "es"], "locales": { "es": { "code": "es", "name": "Spanish", "nativeName": "EspaΓ±ol", "locale": "es_ES", "direction": "ltr" } } } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "languages": { "supported": ["en", "bn", "ar", "es"], "locales": { "es": { "code": "es", "name": "Spanish", "nativeName": "EspaΓ±ol", "locale": "es_ES", "direction": "ltr" } } } } CODE_BLOCK: { "languages": { "supported": ["en", "bn", "ar", "es"], "locales": { "es": { "code": "es", "name": "Spanish", "nativeName": "EspaΓ±ol", "locale": "es_ES", "direction": "ltr" } } } } CODE_BLOCK: mkdir -p app/(protected)/@moderator/dashboard Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: mkdir -p app/(protected)/@moderator/dashboard CODE_BLOCK: mkdir -p app/(protected)/@moderator/dashboard CODE_BLOCK: if (currentUser?.role === 'moderator') return moderator Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: if (currentUser?.role === 'moderator') return moderator CODE_BLOCK: if (currentUser?.role === 'moderator') return moderator COMMAND_BLOCK: npm run dev Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm run dev COMMAND_BLOCK: npm run dev COMMAND_BLOCK: app/ (protected)/ # Routes behind auth @admin/ # Admin-only pages @user/ # User pages layout.tsx # Role-based routing api/ # API routes auth/[...nextauth]/ # NextAuth handler health/ # GET /api/health β†’ { status: "ok" } auth/login/ # Login page unauthorized/ # 403-style page layout.tsx # Root layout (theme, i18n) page.tsx # Landing page not-found.tsx # 404 manifest.ts # PWA manifest from config robots.ts # Robots.txt from config sitemap.ts # Dynamic sitemap from config components/ ui/ # shadcn/ui layout/ # Header, sidebar, theme toggle language-switcher.tsx locales/ en.json, bn.json, ar.json lib/ auth/ # NextAuth options, session, auth context config/ app-main-meta-data.json site.ts # baseUrl, supportedLocales i18n/ get-translations.ts language-context.tsx use-translations.ts types.ts utils.ts e2e/ # Playwright E2E tests .github/workflows/ # CI: check.yml, playwright.yml Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: app/ (protected)/ # Routes behind auth @admin/ # Admin-only pages @user/ # User pages layout.tsx # Role-based routing api/ # API routes auth/[...nextauth]/ # NextAuth handler health/ # GET /api/health β†’ { status: "ok" } auth/login/ # Login page unauthorized/ # 403-style page layout.tsx # Root layout (theme, i18n) page.tsx # Landing page not-found.tsx # 404 manifest.ts # PWA manifest from config robots.ts # Robots.txt from config sitemap.ts # Dynamic sitemap from config components/ ui/ # shadcn/ui layout/ # Header, sidebar, theme toggle language-switcher.tsx locales/ en.json, bn.json, ar.json lib/ auth/ # NextAuth options, session, auth context config/ app-main-meta-data.json site.ts # baseUrl, supportedLocales i18n/ get-translations.ts language-context.tsx use-translations.ts types.ts utils.ts e2e/ # Playwright E2E tests .github/workflows/ # CI: check.yml, playwright.yml COMMAND_BLOCK: app/ (protected)/ # Routes behind auth @admin/ # Admin-only pages @user/ # User pages layout.tsx # Role-based routing api/ # API routes auth/[...nextauth]/ # NextAuth handler health/ # GET /api/health β†’ { status: "ok" } auth/login/ # Login page unauthorized/ # 403-style page layout.tsx # Root layout (theme, i18n) page.tsx # Landing page not-found.tsx # 404 manifest.ts # PWA manifest from config robots.ts # Robots.txt from config sitemap.ts # Dynamic sitemap from config components/ ui/ # shadcn/ui layout/ # Header, sidebar, theme toggle language-switcher.tsx locales/ en.json, bn.json, ar.json lib/ auth/ # NextAuth options, session, auth context config/ app-main-meta-data.json site.ts # baseUrl, supportedLocales i18n/ get-translations.ts language-context.tsx use-translations.ts types.ts utils.ts e2e/ # Playwright E2E tests .github/workflows/ # CI: check.yml, playwright.yml - Three languages out of the box: English, বাংলা (Bengali), and Ψ§Ω„ΨΉΨ±Ψ¨ΩŠΨ© (Arabic) - RTL support that actually works: Arabic layouts automatically flip to right-to-left - Dead-simple language switching: One click, zero page reload - Type-safe with TypeScript: Your IDE will tell you when a translation is missing - Google OAuth – enable by setting GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true in .env - Custom login page at /auth/login with optional "Sign in with Google" - Admin role by email – set [email protected] (comma-separated); those Google accounts get the admin role automatically - JWT session with role and user id; redirect to /dashboard after sign-in - Components are copy-paste ready (no bloated dependencies) - Full TypeScript support - Accessible by default (WCAG compliant) - Easy to customize without fighting CSS - Catch real bugs (unused variables, missing dependencies, potential null references) - Enforce consistency (import order, naming conventions, formatting) - Don't get in your way (no annoying warnings for things that don't matter) - Work with Next.js 15 (proper App Router support, server component rules) - eslint-config-next - Official Next.js rules - TypeScript-specific linting - Import sorting and organization - Best practices for React hooks - Accessibility checks (a11y) - Open Graph tags - Twitter cards - Structured data (JSON-LD) - Multi-language meta tags - Canonical URLs - Dynamic sitemap generation - Unit and component tests: Vitest + React Testing Library. Run npm run test or npm run test:watch; npm run test:coverage for coverage. - E2E: Playwright in e2e/. Run npm run e2e (dev server starts automatically); npm run e2e:ui for the UI. Use npm run e2e:webkit for WebKit-only (e.g. to save disk). - CI: GitHub Actions – .github/workflows/check.yml runs lint, Prettier, tests, and build on push/PR; .github/workflows/playwright.yml runs E2E. - Create locales/es.json with the same structure as locales/en.json: - Update lib/config/app-main-meta-data.json: - In lib/i18n/get-translations.ts, import es.json and add es to the translations object. If you use strict translation keys, add the new locale to the TranslationKeys union in lib/i18n/types.ts. - Create a new parallel route folder: - Add your pages inside that folder - Update app/(protected)/layout.tsx to handle the new role: - Node.js 18.17 or later - npm, yarn, pnpm, or bun - SaaS products with multiple user types - International applications - MVPs that need to look professional - Projects where you want to ship fast - Simple landing pages (too much infrastructure) - Projects with very specific auth requirements (you'll need to customize heavily) - Apps that don't need i18n or role management - Parallel routes are underrated: They make role-based routing so much cleaner than conditional rendering - Type-safe i18n is worth the setup: Catching translation bugs at compile time saves hours of debugging - JSON configuration > hardcoded values: When you can change your entire SEO strategy by editing one file, you move faster - Boilerplates should be opinionated: Too many options = decision fatigue. I made the tough choices so you don't have to - Joined Feb 8, 2026