DEV Community

Junichi Takahashi
Junichi Takahashi

Posted on

Next.js "Getting Started" Stack

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
Enter fullscreen mode Exit fullscreen mode

Platforms

- App
  - Vercel
- Storage
  - Vercel Blob
- Database
  - Neon
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Wrap-up

I’d love to hear your favorite "getting started" stack too-what are you using right now, and why? 🙌

Top comments (0)