My Favorite Next.js “Getting Started” Stack (2025)
A quick plug
I built starter prompts so a coding agent can spin up my go-to Next.js starter stack in no time🚀
https://www.kakuco.dev/
Quick overview
Libraries
- Lint
- Biome
- Test
- Vitest
- CI/CD
- Husky
- GitHub Actions
- Schema
- Zod
- Form
- React Hook Form
- shadcn/ui
- Database
- Prisma
- Auth
- Clerk
- Payment
- Stripe
- UI
- Tailwind CSS
- UI - theme
- next-themes
- Analytics
- Google Analytics
- @next/third-parties
- vanilla-cookieconsent
Platforms
- App
- Vercel
- Storage
- Vercel Blob
- Database
- Neon
Directory layout
src/
├─ _lib/
├─ _actions/
│ └─ domain/
│ └─ todo.ts
├─ _schemas/
│ └─ domain/
│ └─ todo.ts
├─ _services/
│ ├─ app/
│ └─ domain/
│ └─ todo.ts
├─ _components/
│ ├─ ui/
│ └─ domain/
│ └─ domain/
│ ├─ form.tsx
│ └─ list.tsx
├─ _hooks/
└─ app/
├─ examples/
│ ├─ [id]/
│ │ └─ page.tsx
│ └─ page.tsx
└─ page.tsx
Why I chose each one
As of 2025, I skip explaining the parts that feel like the de-facto choices.
Linting
I considered:
-
ESLint
+Prettier
Biome
Why I picked that:
- Simple configuration
- Fast
Note: Tailwind class sorting isn’t built in yet, so I use the Tailwind CSS IntelliSense
VS Code extension to handle ordering.
Testing
- /
CI/CD
- /
Schema
- /
Forms
- /
Database
I considered:
Prisma
Drizzle ORM
Why I picked that:
- Stable behavior
- Schema files are easy to read
Auth
I considered both hosted providers and roll-your-own:
Auth0
Clerk
NextAuth
Better Auth
Why I picked that:
- Hosted provider
- Pricing
- Good DX
Payments
- /
UI
- /
UI — Theme switching
Supports toggling themes.
"use client";
import {
MoonIcon as DarkModeIcon,
SunIcon as RootModeIcon,
} from "lucide-react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { Button } from "@/_components/ui/button";
export default function Component() {
const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const next = resolvedTheme === "dark" ? "light" : "dark";
return (
<Button onClick={() => setTheme(next)} variant="ghost" size="icon">
{mounted ? (
resolvedTheme === "dark" ? (
<RootModeIcon />
) : (
<DarkModeIcon />
)
) : null}
</Button>
);
}
Analytics
Wait for cookie consent before loading analytics tags.
"use client";
import { GoogleAnalytics } from "@next/third-parties/google";
import { useEffect, useState } from "react";
import "vanilla-cookieconsent/dist/cookieconsent.css";
import * as CookieConsent from "vanilla-cookieconsent";
export default function Component({ gaId }: { gaId: string | undefined }) {
const [acceptedAnalytics, setAcceptedAnalytics] = useState(false);
useEffect(() => {
CookieConsent.run({
categories: {
necessary: { enabled: true, readOnly: true },
analytics: {},
},
language: {
default: "en",
translations: {
en: {
consentModal: {
title: "We use cookies",
description: "Cookie modal description",
acceptAllBtn: "Accept all",
acceptNecessaryBtn: "Reject all",
showPreferencesBtn: "Manage Individual preferences",
},
preferencesModal: {
title: "Manage cookie preferences",
acceptAllBtn: "Accept all",
acceptNecessaryBtn: "Reject all",
savePreferencesBtn: "Accept current selection",
closeIconLabel: "Close modal",
sections: [
{
title: "Somebody said ... cookies?",
description: "I want one!",
},
{
title: "Strictly Necessary cookies",
description:
"These cookies are essential for the proper functioning of the website and cannot be disabled.",
// this field will generate a toggle linked to the 'necessary' category
linkedCategory: "necessary",
},
{
title: "Performance and Analytics",
description:
"These cookies collect information about how you use our website. All of the data is anonymized and cannot be used to identify you.",
linkedCategory: "analytics",
},
{
title: "More information",
description:
'For any queries in relation to my policy on cookies and your choices, please <a href="#contact-page">contact us</a>',
},
],
},
},
},
},
onConsent() {
setAcceptedAnalytics(CookieConsent.acceptedCategory("analytics"));
},
});
}, []);
if (gaId == null) {
return;
}
return acceptedAnalytics ? <GoogleAnalytics gaId={gaId} /> : null;
}
Wrap-up
I’d love to hear your favorite "getting started" stack too-what are you using right now, and why? 🙌
Top comments (0)