Better-TS-Stack
Modules

Frontend Module

Next.js 16 or TanStack Start with React 19 and Tailwind CSS v4

Overview

The Frontend module generates a full-stack React application. When you choose the Full-stack application type, the CLI prompts you to pick between Next.js 16 (App Router) and TanStack Start (RC). Both ship with React 19 and Tailwind CSS v4.

The frontend module produces a single project at the project root. There are no separate frontend/ or backend/ subdirectories.

What You Get

app/
|-- layout.tsx       # Root layout with fonts and metadata
|-- page.tsx         # Home page
\-- globals.css      # Tailwind CSS v4 import + base styles
components/ui/       # shadcn-compatible base UI primitives
lib/utils.ts         # cn() helper for class merging
components.json      # shadcn CLI-compatible component config
public/              # Static assets (Next.js defaults)
proxy.ts             # Dev proxy configuration
next.config.ts       # Next.js configuration
postcss.config.mjs   # PostCSS setup for Tailwind
tsconfig.json        # TypeScript configuration
eslint.config.mjs    # ESLint configuration
next-env.d.ts        # Next.js TypeScript environment types

Every generated Next.js app now includes a shadcn-compatible Base UI foundation so future auth, dashboard, and product screens can reuse the same component patterns immediately.


Technology Stack

TechnologyVersionPurpose
Next.js16.3.4React framework with App Router
React19.2.8UI library
TypeScript6.0Type safety
Tailwind CSS4.xUtility-first CSS

Generated Files

app/layout.tsx

Root layout with Geist fonts and base metadata:

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";

import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

app/globals.css

Tailwind CSS v4 setup with system dark-mode support:

@import "tailwindcss";

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  background: var(--background);
  color: var(--foreground);
  font-family: Arial, Helvetica, sans-serif;
}

Commands

npm run dev    # next dev
npm run build  # next build
npm run start  # next start
npm run lint   # eslint

Integration with Auth Module

If Better Auth is selected (requires a database), the auth module adds:

  • lib/auth.ts - server-side Better Auth configuration with the matching adapter
  • lib/auth-client.ts - createAuthClient() for use in client components
  • lib/auth-schema.ts - shared Zod validation and auth error formatting
  • app/api/auth/[...all]/route.ts - the Better Auth route handler
  • app/sign-in/page.tsx - login route with server-side redirect when signed in
  • app/sign-up/page.tsx - signup route with server-side redirect when signed in
  • app/dashboard/page.tsx - protected route example with server-side session check
  • components/auth/* - login, signup, and sign-out UI components
// lib/auth.ts
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
import { prismaAdapter } from "better-auth/adapters/prisma";
import client from "./prisma";

export const auth = betterAuth({
  database: prismaAdapter(client, { provider: "postgresql" }),
  emailAndPassword: { enabled: true },
  plugins: [nextCookies()],
  secret: process.env.BETTER_AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,
});

Auth route handler

// app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";

import { auth } from "@/lib/auth";

export const { GET, POST } = toNextJsHandler(auth);

Client-side auth

// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient();

The generated sign-in and sign-up forms use React Hook Form + Zod on the client and call authClient.signIn.email(...) / authClient.signUp.email(...).

BETTER_AUTH_SECRET and BETTER_AUTH_URL are server-side variables. Do not prefix them with NEXT_PUBLIC_.


Environment Variables

NODE_ENV=development
PORT=3000

With Better Auth added:

BETTER_AUTH_SECRET=please-change-me-to-a-random-string
BETTER_AUTH_URL=http://localhost:3000

With a database added, the relevant DATABASE_URL or MONGODB_URI is also included.


TanStack Start

TanStack Start uses the Vite dev server, TanStack Router (type-safe file-based routing), and React Query with SSR. It is available in the full-stack framework prompt.

src/
|-- routes/           # File-based routes (__root.tsx, index.tsx)
|-- routeTree.gen.ts  # Generated route tree
|-- router.tsx        # createRouter() + QueryClient + SSR query integration
|-- lib/utils.ts      # cn() helper for class merging
|-- components/ui/    # shadcn-compatible base UI primitives
|-- styles/app.css    # Tailwind CSS v4 import + base styles
vite.config.ts        # tanstackStart() + tailwind + react plugins
components.json       # shadcn CLI-compatible component config

Key conventions

  • ~/* path alias mapped to ./src/* (TanStack-native, resolved by tsconfigPaths: true in the Vite config). Use ~/components/... and ~/lib/... in imports.
  • File-based routing lives in src/routes/. TanStack Router regenerates src/routeTree.gen.ts during Vite development and builds.
  • No Nitro: the build produces dist/server/server.js (a fetch-compatible handler) plus dist/client. The start script serves it with srvx:
npm run build   # vite build
npm run start   # srvx --prod -s ../client dist/server/server.js
npm run dev     # vite dev
npm run type:check  # tsc --noEmit
npm run lint    # eslint src
  • PORT controls the server port (default 3000).
  • The generated package.json sets "type": "module" so the ESM-only TanStack Vite plugin loads correctly.

Commands

bun run dev       # vite dev
bun run build     # vite build
bun run start     # srvx --prod -s ../client dist/server/server.js
bun run type:check
bun run lint

Integration with Auth Module

If Better Auth is selected (requires a database), the auth module additionally generates:

  • src/lib/auth.ts - betterAuth() with tanstackStartCookies() and the matching adapter
  • src/lib/auth-client.ts - createAuthClient() for client components
  • src/lib/auth-functions.ts - getSession() server function built on createServerFn + auth.api.getSession
  • src/routes/api/auth/$.ts - the Better Auth route handler via auth.handler(request)
  • src/routes/sign-in.tsx / src/routes/sign-up.tsx - login/signup routes with beforeLoad redirect when signed in
  • src/routes/dashboard.tsx - protected route example using redirect({ to: "/sign-in" }) in beforeLoad
  • src/components/auth/* - login, signup, and sign-out UI components
// src/lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { tanstackStartCookies } from "better-auth/tanstack-start";

import { db } from "./db";

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "pg" }),
  emailAndPassword: { enabled: true },
  plugins: [tanstackStartCookies()],
  secret: process.env.BETTER_AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,
});

setupRouterSsrQueryIntegration connects TanStack Query to router SSR. Authentication is checked separately by getSession() in each route's beforeLoad, and protected routes expose the returned user through route context.


Extending Next.js

Add a page

// app/about/page.tsx
export default function AboutPage() {
  return (
    <main>
      <h1>About</h1>
    </main>
  );
}

Add a server-side API route

// app/api/hello/route.ts
export async function GET() {
  return Response.json({ message: "Hello from Next.js!" });
}

Add a component

// components/Header.tsx
export function Header() {
  return (
    <header className="border-b p-4">
      <nav>Navigation here</nav>
    </header>
  );
}

Components that use useState, useEffect, or event handlers must have "use client" at the top of the file. All other components default to Server Components.

On this page