Every time I started a new Next.js project, I lost the first week to setup.
Authentication. Internationalization. Role-based access. SEO meta tags. Environment validation. Error monitoring. Linting. Testing. CI pipelines.
By the time I had a working foundation, the excitement was gone, buried under config files and glue code.
I kept rebuilding the same foundation across SaaS projects, so I stopped and built it once: Next Elite.
It is a frontend-first Next.js 16 + React 19 starter with BetterAuth, permission-based RBAC, type-safe i18n, testing, and CI. MIT-licensed and open source. No database layer included. You bring your own API.
Live Demo · GitHub Repo · Use This Template · Deploy on Vercel
TL;DR:
git clone,npm install,cp .env.example .env,npm run dev. You get auth, RBAC dashboards, 6-language i18n, SEO, forms, testing, and CI. Try the demo.
Table of Contents
- What's Included
- Lighthouse Scores
- Who It's For
- Getting Started
- How It Works
- Project Structure
- Caveats
- Production Checklist
- Contributing
What's Included
Most Next.js starters are either too bare (a theme toggle and a TODO for auth) or too opinionated (Prisma, a specific DB, and an ORM baked in). Next Elite sits in the middle: it handles the frontend foundation and leaves the data layer to you.
Stack
- Next.js 16 + React 19 (App Router, Server Components)
- TypeScript 6, Tailwind CSS v4, Zod 4
- Node.js 22 + npm 11 in CI
Auth & access
- BetterAuth (email/password + optional Google OAuth)
- Permission-based RBAC with
requireUserandrequirePermission - Parallel routes (
@admin,@user) for role-based dashboards
i18n & SEO
- next-intl with 6 languages (English, বাংলা, العربية with RTL, Français, Español, 简体中文)
- Cookie-based locale, compile-time checked translation keys
- One
site.config.jsonfile for OG tags, JSON-LD, sitemap, robots, and manifest
Data & forms
- TanStack Query + ofetch (
apiFetchwith Zod parsing) - React Hook Form + Zod 4 for auth forms
DX & ops
- T3 Env for validated environment variables
- Sentry + pino logging + optional Upstash rate limiting
- Vitest, Playwright, Lefthook, Commitlint 21, Knip, ESLint 9, Prettier
- GitHub Actions CI, Renovate, Vercel Analytics
- Docker + Vercel deploy (
output: 'standalone') - Demo mode for quick local previews
Lighthouse Scores
The production build scores 100 in Performance, Accessibility, Best Practices, and SEO.
Tested on the live demo at next-elite-boilerplate.vercel.app. Server components by default, client components only where needed.
Who It's For
Good fit:
- SaaS apps with multiple user roles
- Internationalized products (LTR and RTL)
- Frontends that consume an existing API or BFF
Not a good fit:
- Single-page landing sites
- Apps that need a database layer in the same repo
Getting Started
Prerequisites: Node.js 22.12+, npm.
git clone https://github.com/salmanshahriar/Next-Elite.git
cd Next-Elite
npm install
cp .env.example .env
npm run dev
Open http://localhost:3000.
Demo login (enabled by default with NEXT_PUBLIC_DEMO_MODE=true):
| Role | Password | |
|---|---|---|
| User | user@test.com |
12345678 |
| Admin | admin@test.com |
12345678 |
Brand your app: edit src/features/site/site.config.json.
Key env vars (full list in .env.example):
-
BETTER_AUTH_SECRET: 32+ chars in production (openssl rand -base64 32) -
BETTER_AUTH_URL: optional, auto-derived on Vercel -
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=trueplus Google OAuth credentials -
SKIP_ENV_VALIDATION=truefor CI/Docker builds without secrets
Deploy on Vercel:
Copy env vars from .env.example into your Vercel project.
Deploy with Docker (Node 22 Alpine):
docker build -t next-elite .
docker run --rm --env-file .env -p 3000:3000 next-elite
Or docker compose up --build.
Scripts:
| Command | What it does |
|---|---|
npm run dev |
Dev server (Turbopack) |
npm run build |
Production build |
npm run start |
Production server |
npm run analyze |
Bundle analysis |
npm run typecheck |
tsc --noEmit |
npm run lint |
ESLint + Prettier check |
npm run lint:fix |
Auto-fix lint and format |
npm run knip |
Find unused files, exports, and dependencies |
npm run check |
typecheck + lint + knip + tests |
npm run test |
Vitest |
npm run e2e |
Playwright (starts dev server automatically) |
npm run e2e:ui |
Playwright UI mode |
npm run e2e:webkit |
WebKit only |
How It Works
Authentication
BetterAuth runs at /api/auth/* via src/app/api/auth/[...all]/route.ts.
- Email/password works out of the box
- Google OAuth: set
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET, andNEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true - Server:
getCurrentUser()in Server Components - Client:
useAuth()in the auth provider - Admin role: comma-separated emails in
AUTH_ADMIN_EMAILS
For multi-instance production, add a BetterAuth storage adapter (database or Redis). See the BetterAuth docs.
Demo mode lives in src/features/auth/demo/. Delete that folder or set NEXT_PUBLIC_DEMO_MODE=false before shipping.
RBAC
Permissions are checked on the server, not with scattered if (role === 'admin') checks.
import { requirePermission } from '@/features/auth/rbac/require';
const AdminDashboardPage = async () => {
const user = await requirePermission('dashboard.view:admin');
return <h1>Welcome {user.email}</h1>;
};
export default AdminDashboardPage;
Parallel routes pick the right dashboard:
src/app/(protected)/
@admin/dashboard/ Admin dashboard
@user/dashboard/ User dashboard
layout.tsx Picks slot based on permissions
To add a role: update permissions.ts, map it in roles.ts, and optionally add a new @<role>/ slot in (protected)/layout.tsx.
Request flow
- Server Component renders the page
-
requireUser()orrequirePermission()checks the session - HTML goes to the browser with translations from
messages/ - Client fetches live data via TanStack Query and
apiFetch
i18n
Locale is stored in a cookie (NEXT_LOCALE), not in the URL path.
To add a language:
- Add the locale to
languages.supportedandlanguages.localesinsite.config.json - Create
messages/<locale>.jsonbased onmessages/en.json
SEO
src/features/site/site.config.json drives layout meta tags, sitemap, robots.txt, manifest, and locale config. Validated with Zod in src/features/site/config.ts.
{
"appName": "Next Elite",
"domain": "https://yourdomain.com",
"title": "Next Elite - Production-Ready SaaS Boilerplate",
"description": "Frontend-first Next.js 16 + React 19 boilerplate with i18n, RBAC and BetterAuth.",
"languages": {
"supported": ["en", "bn", "ar", "fr", "es", "zh"],
"default": "en"
},
"organization": {
"name": "Your Organization",
"url": "https://yourdomain.com"
},
"images": {
"og": "/Nextjs-Elite-OG-Image.webp",
"cover": "/Nextjs-Elite-Cover.webp"
},
"manifest": "/manifest.webmanifest"
}
API layer
apiFetch in src/libs/api-client.ts uses ofetch with Zod parsing. The users feature is an example pattern. You wire up /api/users or point NEXT_PUBLIC_APP_URL at your backend.
// src/features/users/api.ts
export async function getUsers(): Promise<User[]> {
return apiFetch('/users', { schema: userListSchema });
}
Forms
Auth forms use React Hook Form with Zod 4 resolvers. Schemas are in src/features/auth/schemas/.
Environment variables
Validated in src/libs/env.ts via T3 Env. BETTER_AUTH_URL is optional (derived from VERCEL_URL on Vercel). Missing BETTER_AUTH_SECRET in production logs a warning instead of crashing the build.
UI
shadcn/ui components in src/components/ui/. Custom theme provider in src/features/theme/ (light, dark, system). No next-themes dependency.
Testing & CI
- Unit tests: Vitest + React Testing Library in
tests/and colocated*.test.ts(x)files - E2E: Playwright in
e2e/across Chromium, Firefox, and WebKit - CI:
.github/workflows/check.ymlruns typecheck, lint, knip, tests, and build - Health endpoint:
GET /api/healthreturns{ "status": "ok" }
proxy.ts is a pass-through middleware. Auth and locale run in Server Components and the next-intl request config.
Project Structure
Feature-sliced layout. Each feature owns its components, hooks, schemas, and server logic. Shared infra lives in libs/.
.
├── .github/workflows/ check.yml + playwright.yml
├── config/ vitest config
├── e2e/ Playwright specs
├── messages/ translations (en, bn, ar, fr, es, zh)
├── tests/ auth and i18n specs
├── src/
│ ├── app/ App Router
│ │ ├── (auth)/ login, register, password reset
│ │ ├── (public)/ marketing pages
│ │ ├── (protected)/ @admin and @user dashboard slots
│ │ └── api/ BetterAuth + health
│ ├── components/ui/ shadcn primitives
│ ├── features/
│ │ ├── auth/ BetterAuth, RBAC, demo
│ │ ├── i18n/ next-intl config
│ │ ├── site/ site.config.json
│ │ └── users/ example API feature
│ └── libs/ env, api-client, logger, utils
└── package.json
Caveats
-
No database. The
usersclient expects your/api/usersor an external API. No mock DB ships with the repo. -
Starter RBAC. Two roles (
user,admin). Admin is an email allowlist, not a DB table. Extend in your backend for multi-tenant needs. -
Cookie-based locale. No
/en/or/fr/URL prefixes. Simpler UX, but not path-prefixed locales for multilingual SEO. - BetterAuth sessions. Default storage works locally. Use a storage adapter for multi-instance production.
-
Rate limiting.
getRateLimiter()exists insrc/libs/rate-limit.tsbut is not wired to routes by default. - Test coverage. RBAC/i18n unit tests and light E2E. Not full app coverage.
Production Checklist
- Set
BETTER_AUTH_SECRET(32+ characters) - Add a BetterAuth storage adapter for multi-instance deploys
- Set
NEXT_PUBLIC_DEMO_MODE=falseor deletesrc/features/auth/demo/ - Implement
/api/usersor connect your real API - Update
site.config.jsonwith your real domain and OG image - Optionally enable Sentry and Upstash rate limiting
Contributing
- Fork and branch from
main(feat/...,fix/...) - Run
npm run checklocally - Use Conventional Commits (enforced by Lefthook)
- Open a PR
Report a bug · Star on GitHub · Submit a PR
MIT licensed. If this saves you setup time, a star on GitHub helps others find it.



Top comments (22)
50 hours is an honest number — most boilerplate authors underestimate the invisible cost of the glue layer (auth callbacks, RBAC middleware, i18n routing edge cases). One thing I'd push on: how does the RBAC model handle permission inheritance vs. flat role assignment? In my experience that's the first thing that breaks when a real client asks for 'editor who can publish but only their own content'. The difference between a boilerplate that ships fast and one that survives the first feature request is usually that one decision.
Great point!
and you’re exactly right that RBAC inheritance is where “simple boilerplate auth” usually collapses.
In this boilerplate, the model is intentionally flat role assignment + explicit permission checks, with ownership-aware rules layered in policy helpers (for cases like “publish own only”). That keeps the base predictable, but it does mean inheritance trees (e.g. admin > editor > author) are not first-class by default.
This is awesome
Good work
Thanks man ‼️
Thats amazingg
Thanks ‼️
Nice simple design. I'll surely use it in the future.
❤️❤️
Mad respect man
Thanks‼️
Nice! Bookmarked, and kept for "whenever I need it"
Thanks, man! Feel free to share any suggestions anytime! ❤️
Coolest shit, needed this!
Thanks, man! Feel free to share any suggestions anytime! ❤️
damn! Good Work brother.
thanks!
Thanks for this blog.
How you DOINNNN xD
Some comments may only be visible to logged-in visitors. Sign in to view all comments.