Getting Started with Next.js#
Set up a modern Next.js project with TypeScript, Tailwind CSS, and Better Auth.
Create New Project#
# Create app with all recommended options
npx create-next-app@latest my-app \
--typescript \
--app \
--tailwind \
--eslint \
--src-dir false \
--import-alias "@/*"
cd my-app
Installation Options Explained#
--typescript- TypeScript support--app- Use App Router (modern)--tailwind- Tailwind CSS for styling--eslint- ESLint for code quality--src-dir false- No src folder (keep app/ at root)--import-alias "@/*"- Import paths like@/components/...
Project Structure#
my-app/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page (/)
│ └── globals.css # Global styles
├── components/ # Reusable components
├── lib/ # Utilities, helpers, auth
├── public/ # Static assets
├── types/ # TypeScript types
├── next.config.js # Next.js configuration
├── tailwind.config.ts # Tailwind configuration
├── tsconfig.json # TypeScript configuration
└── package.json
Install Dependencies#
# Better Auth for authentication
npm install better-auth
# Zod for validation
npm install zod
# React Hook Form (optional, for forms)
npm install react-hook-form @hookform/resolvers
# UI primitives (optional)
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu
# Utilities
npm install clsx tailwind-merge class-variance-authority
Root Layout#
// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "My App",
description: "Built with Next.js and Better Auth",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
</html>
);
}
Home Page#
// app/page.tsx
export default function Home() {
return (
<main className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4">
Welcome to Next.js
</h1>
<p className="text-gray-600">
Your app is ready to go!
</p>
</div>
</main>
);
}
Run Development Server#
npm run dev
Visit http://localhost:3000
Environment Variables#
# .env.local
DATABASE_URL="postgresql://..."
BETTER_AUTH_SECRET="your-secret-key"
BETTER_AUTH_URL="http://localhost:3000"
Never commit .env.local - add to .gitignore
Folder Organization#
Route Groups (Recommended)#
Use parentheses to group routes without affecting URLs:
app/
├── (auth)/ # Auth pages (login, signup)
│ ├── login/
│ │ └── page.tsx → /login
│ └── signup/
│ └── page.tsx → /signup
├── (protected)/ # Protected routes
│ ├── dashboard/
│ │ └── page.tsx → /dashboard
│ ├── profile/
│ │ └── page.tsx → /profile
│ └── layout.tsx # Layout for protected pages
└── page.tsx → /
Component Organization#
components/
├── ui/ # Reusable UI components
│ ├── button.tsx
│ ├── input.tsx
│ └── card.tsx
├── auth/ # Auth-specific components
│ ├── login-form.tsx
│ └── signup-form.tsx
└── shared/ # Shared across app
├── navbar.tsx
└── footer.tsx
Lib Organization#
lib/
├── auth.ts # Better Auth config
├── db.ts # Database connection
├── utils.ts # Utility functions
└── validations.ts # Zod schemas
TypeScript Configuration#
// tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Next.js Configuration#
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Allow images from external domains
images: {
domains: ['avatars.githubusercontent.com'],
},
// Enable strict mode
reactStrictMode: true,
}
module.exports = nextConfig
Utility Functions#
cn() - Merge Tailwind Classes#
// lib/utils.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage
<div className={cn("bg-blue-500", someCondition && "bg-red-500")} />
Import Aliases#
// ✅ With @ alias
import { Button } from "@/components/ui/button";
import { auth } from "@/lib/auth";
// ❌ Without alias
import { Button } from "../../components/ui/button";
import { auth } from "../lib/auth";
Development Scripts#
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}
npm run dev # Start development server
npm run build # Build for production
npm run start # Start production server
npm run lint # Run ESLint
Debugging#
Enable Source Maps#
// next.config.js
const nextConfig = {
productionBrowserSourceMaps: true,
}
React DevTools#
Install React DevTools browser extension.
Next.js DevTools#
Built-in - accessible at http://localhost:3000 during development.
Gitignore#
# .gitignore
node_modules/
.next/
.env*.local
.vercel
*.log
Best Practices#
- ✅ Use TypeScript - Type safety prevents bugs
- ✅ Configure path aliases - Use
@/imports - ✅ Organize by feature - Group related files together
- ✅ Use route groups -
(auth),(protected)for organization - ✅ Environment variables - Never commit secrets
- ✅ Enable strict mode - Catch bugs early
Quick Reference#
| Command | Description |
|---|---|
npm run dev |
Start dev server on port 3000 |
npm run build |
Build for production |
npm run start |
Start production server |
npx next info |
Show Next.js version info |
Next Steps#
- Better Auth Setup - Add authentication
- Auth UI - Build login and signup pages