A modern, full-stack starter template for building dynamic web applications with Next.js. This template includes everything you need to create data-driven applications with authentication, database integration, and state management - all built with modern technologies and best practices.
Tip
If you're looking for a Static Website Template, check out the Next.js Starter Template
If you're looking for a Frontend-Only Template (Dynamic), check out the Next.js Template for External APIs
- Features
- Project Structure
- Example Files
- Tech Stack
- Getting Started
- Available Scripts
- Development Guide
- Deployment
- Next.js 15 with App Router and TypeScript
- React 19 with full server/client components support
- Turbopack for lightning-fast development builds
- ESLint configuration for code quality
- TypeScript for type safety throughout the application
- Prisma ORM for type-safe database operations
- PostgreSQL database support (with Prisma Cloud)
- Database migrations and seeding
- API Routes with Next.js App Router
- Server-side data fetching and caching
- TanStack Query (React Query) for server state management
- React Query DevTools for debugging
- Optimistic updates and background refetching
- Infinite queries and pagination support
- Tailwind CSS 4 for modern, utility-first styling
- HeroUI component library with full theme support
- Shadcn/ui components integration
- Dark/Light theme switching with next-themes
- Framer Motion for smooth animations and transitions
- Lucide React for beautiful, consistent icons
- Hot reloading with Turbopack
- Type-safe database queries with Prisma
- Automatic type generation from database schema
- Built-in development tools and debugging
- Production-ready build configuration
src/
├── app/
│ ├── globals.css # Global styles and Tailwind imports
│ ├── layout.tsx # Root layout with providers
│ ├── page.tsx # Home page component
│ ├── api/ # API Routes
│ │ ├── posts/ # Example Posts API
│ │ └── comments/ # Example Comments API
│ └── posts/ # Example CRUD pages
├── components/
│ └── ui/
│ └── drawer.tsx # Shadcn/ui drawer component
├── lib/
│ ├── prisma.ts # Prisma client singleton
│ └── utils.ts # Utility functions (cn helper)
└── providers/
└── initial.tsx # App providers (Theme, HeroUI, TanStack Query)
prisma/
├── schema.prisma # Database schema definition
├── seed.ts # Database seeding script
└── dev.db # SQLite database (dev)
.env # Environment variables (DATABASE_URL)This template comes with a fully functional example including:
- Database Models:
PostandCommentmodels inprisma/schema.prisma. - API Routes: RESTful endpoints in
src/app/api/postsandsrc/app/api/comments. - Frontend: A CRUD interface for Posts in
src/app/posts. - Seeding: Sample data generation in
prisma/seed.ts.
These examples demonstrate how to integrate Prisma, TanStack Query, and HeroUI. You can use them as a reference or remove them to start fresh.
- Next.js 15 - React framework with App Router
- React 19 - UI library with server/client components
- TypeScript - Type safety and developer experience
- Prisma - Next-generation ORM for Node.js and TypeScript
- PostgreSQL - Robust relational database
- Prisma Client - Type-safe database client
- TanStack Query - Powerful data synchronization for React
- React Query DevTools - Debugging and development tools
- Tailwind CSS 4 - Utility-first CSS framework
- HeroUI - Modern React component library
- Shadcn/ui - Re-usable components (drawer component included)
- Lucide React - Beautiful & consistent icon toolkit
- Framer Motion - Animation library
- next-themes - Theme switching support
- Vaul - Drawer component primitive
- class-variance-authority - Component variant utility
- clsx - Conditional className utility
- tailwind-merge - Tailwind class merging
- Node.js 18+ (LTS recommended)
- npm, yarn, or pnpm package manager
- PostgreSQL database (local installation or cloud service)
# Clone the repository
git clone https://github.com/your-username/nextjs-template.git
cd nextjs-template
# Or download and extract the template filesnpm install
# or
yarn install
# or
pnpm installCreate a .env file in the root directory:
# Create environment file
touch .envAdd your database connection string to .env:
# Database Connection
DATABASE_URL="postgresql://username:password@localhost:5432/your_database_name"
# For Prisma Cloud (recommended for development)
# DATABASE_URL="prisma+postgres://your-connection-string"Database URL Examples:
- Local PostgreSQL:
postgresql://postgres:password@localhost:5432/myapp - Railway:
postgresql://postgres:password@containers-us-west-1.railway.app:7954/railway - Supabase:
postgresql://postgres:password@db.project.supabase.co:5432/postgres - Prisma Cloud:
prisma+postgres://accelerate.prisma-data.net/?api_key=your-api-key
- Visit Prisma Cloud and create a free account
- Create a new database project
- Copy the connection string to your
.envfile - Run the migration:
npx prisma migrate dev --name init- Install PostgreSQL on your system
- Create a new database:
CREATE DATABASE your_app_name;- Update the
DATABASE_URLin.envwith your local connection details - Run the migration:
npx prisma migrate dev --name initnpx prisma generatenpm run seed
# or
npx prisma db seednpm run dev
# or
yarn dev
# or
pnpm devNavigate to http://localhost:3000 in your browser.
Your application should now be running with:
- Database connected and migrated
- Sample data seeded (if you ran the seed command)
- API endpoints working (
/api/example) - UI components and theming functional
- TanStack Query configured for data fetching
If you want to start a fresh project without the example files (Posts, Comments, Seed data), run the cleanup script:
npm run cleanupWarning
This command will DELETE src/app/posts, src/app/api/posts, src/app/api/comments and RESET prisma/schema.prisma, prisma/seed.ts, and src/app/page.tsx. Make sure to commit your work before running this.
npm run dev- Start development server with Turbopacknpm run build- Build the application for productionnpm run start- Start the production servernpm run lint- Run ESLint for code quality checksnpx prisma studio- Open Prisma Studio (database GUI)npx prisma migrate dev- Create and apply new migrationnpx prisma generate- Generate Prisma Clientnpx prisma db seed- Seed the database with sample datanpx prisma db seed- Seed the database with sample datanpx prisma db push- Push schema changes to database (development)npm run cleanup- Remove example files and reset project to a clean state
Define your data models in prisma/schema.prisma:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}After modifying your schema:
# Create and apply migration
npx prisma migrate dev --name add_user_model
# Reset database (development only)
npx prisma migrate resetUpdate prisma/seed.ts to populate your database:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
// Create sample users
const user1 = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice Johnson',
posts: {
create: [
{
title: 'Hello World',
content: 'This is my first post!',
published: true,
},
],
},
},
});
console.log({ user1 });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});Create type-safe API endpoints in src/app/api/:
// src/app/api/users/route.ts
import prisma from "@/lib/prisma";
import { NextRequest, NextResponse } from "next/server";
export async function GET() {
try {
const users = await prisma.user.findMany({
include: {
posts: true,
},
});
return NextResponse.json(users);
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch users" },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const { email, name } = await request.json();
const user = await prisma.user.create({
data: {
email,
name,
},
});
return NextResponse.json(user, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: "Failed to create user" },
{ status: 500 }
);
}
}// src/app/api/users/[id]/route.ts
import prisma from "@/lib/prisma";
import { NextRequest, NextResponse } from "next/server";
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await prisma.user.findUnique({
where: { id: parseInt(params.id) },
include: {
posts: true,
},
});
if (!user) {
return NextResponse.json(
{ error: "User not found" },
{ status: 404 }
);
}
return NextResponse.json(user);
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch user" },
{ status: 500 }
);
}
}// hooks/useUsers.ts
import { useQuery } from '@tanstack/react-query';
interface User {
id: number;
email: string;
name: string;
posts: Post[];
}
export function useUsers() {
return useQuery<User[]>({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Failed to fetch users');
}
return response.json();
},
});
}// components/UserList.tsx
'use client';
import { useUsers } from '@/hooks/useUsers';
import { Card, Spinner, Button } from '@heroui/react';
export function UserList() {
const { data: users, isLoading, error, refetch } = useUsers();
if (isLoading) {
return (
<div className="flex justify-center p-8">
<Spinner size="lg" />
</div>
);
}
if (error) {
return (
<Card className="p-4">
<p className="text-red-500">Error loading users</p>
<Button onClick={() => refetch()} className="mt-2">
Retry
</Button>
</Card>
);
}
return (
<div className="grid gap-4">
{users?.map((user) => (
<Card key={user.id} className="p-4">
<h3 className="font-semibold">{user.name}</h3>
<p className="text-gray-600">{user.email}</p>
<p className="text-sm">{user.posts.length} posts</p>
</Card>
))}
</div>
);
}// hooks/useCreateUser.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface CreateUserData {
email: string;
name: string;
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (userData: CreateUserData) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error('Failed to create user');
}
return response.json();
},
onSuccess: () => {
// Invalidate and refetch users list
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}HeroUI provides a comprehensive set of pre-built components with built-in theming:
import {
Button,
Card,
Input,
Modal,
ModalContent,
ModalHeader,
ModalBody,
useDisclosure
} from "@heroui/react";
export function UserForm() {
const { isOpen, onOpen, onClose } = useDisclosure();
const createUser = useCreateUser();
const handleSubmit = async (formData: FormData) => {
const email = formData.get('email') as string;
const name = formData.get('name') as string;
try {
await createUser.mutateAsync({ email, name });
onClose();
} catch (error) {
console.error('Failed to create user:', error);
}
};
return (
<>
<Button color="primary" onPress={onOpen}>
Add User
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContent>
<ModalHeader>Create New User</ModalHeader>
<ModalBody>
<form action={handleSubmit} className="space-y-4">
<Input
name="name"
label="Name"
placeholder="Enter user name"
required
/>
<Input
name="email"
type="email"
label="Email"
placeholder="Enter email address"
required
/>
<Button
type="submit"
color="primary"
className="w-full"
isLoading={createUser.isPending}
>
Create User
</Button>
</form>
</ModalBody>
</ModalContent>
</Modal>
</>
);
}The template includes the drawer component. Add more components as needed:
import {
Drawer,
DrawerTrigger,
DrawerContent,
DrawerHeader,
DrawerTitle
} from "@/components/ui/drawer";
export function UserDrawer({ children }: { children: React.ReactNode }) {
return (
<Drawer>
<DrawerTrigger>{children}</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>User Details</DrawerTitle>
</DrawerHeader>
{/* Drawer content */}
</DrawerContent>
</Drawer>
);
}import {
User,
Mail,
Plus,
Edit,
Trash2,
RefreshCw
} from "lucide-react";
export function UserActions() {
return (
<div className="flex gap-2">
<Button isIconOnly variant="ghost">
<Edit size={16} />
</Button>
<Button isIconOnly variant="ghost" color="danger">
<Trash2 size={16} />
</Button>
<Button isIconOnly variant="ghost">
<RefreshCw size={16} />
</Button>
</div>
);
}
## Theme Support
The template includes comprehensive dark/light theme support with automatic system detection:
```tsx
'use client';
import { useTheme } from "next-themes";
import { Button } from "@heroui/react";
import { Sun, Moon } from "lucide-react";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
isIconOnly
variant="ghost"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
{theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />}
</Button>
);
}
Theme Features:
- System theme detection - Automatically matches user's system preference
- Manual theme switching - Programmatic theme control
- HeroUI integration - All components support theming
- Tailwind dark mode - Dark variant classes available
- Persistent theme - Theme preference saved in localStorage
Framer Motion is included for smooth animations:
'use client';
import { motion } from "framer-motion";
export function AnimatedCard({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
className="p-4 rounded-lg bg-white dark:bg-gray-800"
>
{children}
</motion.div>
);
}
// List animations
export function AnimatedList({ items }: { items: any[] }) {
return (
<motion.div
initial="hidden"
animate="visible"
variants={{
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
}}
>
{items.map((item, index) => (
<motion.div
key={item.id}
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
}}
>
{/* Item content */}
</motion.div>
))}
</motion.div>
);
}For production deployment, set the following environment variables:
# Database
DATABASE_URL="your-production-database-url"
# Optional: Analytics, Authentication, etc.
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="https://yourdomain.com"# Build the application
npm run build
# Start production server
npm run start- Connect your GitHub repository to Vercel
- Set environment variables in Vercel dashboard
- Deploy automatically on git push
# Build command
npm run build
# Publish directory
.next- Connect your GitHub repository
- Add environment variables
- Railway automatically detects Next.js
# Build and start with PM2
npm run build
pm2 start npm --name "nextjs-app" -- start- Update Prisma Schema:
// prisma/schema.prisma
model Product {
id Int @id @default(autoincrement())
name String
description String?
price Decimal
category Category @relation(fields: [categoryId], references: [id])
categoryId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Category {
id Int @id @default(autoincrement())
name String @unique
products Product[]
}- Create and Apply Migration:
npx prisma migrate dev --name add_product_category- Generate Types:
npx prisma generate# Add a new component (example: button)
npx shadcn-ui@latest add button// components/ProductCard.tsx
import { Card, CardBody, Button, Chip } from "@heroui/react";
import { ShoppingCart } from "lucide-react";
interface ProductCardProps {
product: {
id: number;
name: string;
price: number;
category: { name: string };
};
}
export function ProductCard({ product }: ProductCardProps) {
return (
<Card className="w-full">
<CardBody className="p-4">
<div className="flex justify-between items-start mb-2">
<h3 className="font-semibold text-lg">{product.name}</h3>
<Chip size="sm" variant="flat">
{product.category.name}
</Chip>
</div>
<p className="text-2xl font-bold text-primary mb-4">
${product.price}
</p>
<Button
color="primary"
startContent={<ShoppingCart size={16} />}
className="w-full"
>
Add to Cart
</Button>
</CardBody>
</Card>
);
}Edit src/app/globals.css for global styles:
/* src/app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
@apply scroll-smooth;
}
body {
@apply bg-background text-foreground;
}
}
@layer components {
.card-hover {
@apply transition-all duration-200 hover:scale-105 hover:shadow-lg;
}
}Use the cn utility function for conditional classes:
import { cn } from "@/lib/utils";
interface ButtonProps {
variant?: "primary" | "secondary";
size?: "sm" | "md" | "lg";
className?: string;
children: React.ReactNode;
}
export function CustomButton({
variant = "primary",
size = "md",
className,
children
}: ButtonProps) {
return (
<button
className={cn(
// Base styles
"rounded-lg font-medium transition-colors",
// Variant styles
variant === "primary" && "bg-blue-500 text-white hover:bg-blue-600",
variant === "secondary" && "bg-gray-200 text-gray-900 hover:bg-gray-300",
// Size styles
size === "sm" && "px-3 py-1.5 text-sm",
size === "md" && "px-4 py-2",
size === "lg" && "px-6 py-3 text-lg",
// Custom className override
className
)}
>
{children}
</button>
);
}
## Dependencies Overview
### Production Dependencies
- `@heroui/react` - Component library with theming
- `@prisma/client` - Type-safe database client
- `@tanstack/react-query` - Server state management
- `@radix-ui/react-dialog` - Accessible dialog primitives
- `next` - React framework with App Router
- `react` & `react-dom` - React library
- `framer-motion` - Animation library
- `next-themes` - Theme switching
- `lucide-react` - Icon library
- `tailwind-merge` - Tailwind class merging
- `clsx` - Conditional classes
- `class-variance-authority` - Component variants
- `vaul` - Drawer component primitive
### Development Dependencies
- `typescript` - Type checking
- `tailwindcss` - CSS framework
- `prisma` - Database toolkit and ORM
- `eslint` & `eslint-config-next` - Code linting
- `@tanstack/eslint-plugin-query` - React Query linting
- `@tanstack/react-query-devtools` - Development tools
- `tsx` - TypeScript execution environment
- `@types/*` - TypeScript definitions
## Troubleshooting
### Common Issues
#### Database Connection Issues
```bash
# Check if your database is running
npx prisma db pull
# Reset migrations (development only)
npx prisma migrate reset
# Check connection string format
echo $DATABASE_URL
# Regenerate Prisma Client
npx prisma generate
# Check TypeScript configuration
npx tsc --noEmit# Clear Next.js cache
rm -rf .next
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install- Use React Query for data fetching - Automatic caching and background updates
- Implement pagination - Use
useInfiniteQueryfor large datasets - Optimize database queries - Use Prisma's
includeandselectstrategically - Enable Next.js caching - Use appropriate cache headers for API routes
- Use Turbopack - Already configured for faster development builds
This template is designed to be a starting point for your projects. Feel free to:
- Fork and customize for your specific needs
- Add new components and features
- Share improvements with the community
- Report issues and suggest enhancements
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Test thoroughly
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is open source and available under the MIT License.
- Documentation: Each technology has comprehensive documentation linked below
- GitHub Issues: Report bugs and request features
- Discussions: Share ideas and get help from the community
- Next.js Documentation - React framework
- React Documentation - UI library
- TypeScript Handbook - Type safety
- Prisma Documentation - Database ORM
- PostgreSQL Documentation - Database
- TanStack Query Documentation - Server state
- React Query DevTools - Debugging
- HeroUI Documentation - Component library
- Tailwind CSS Documentation - Styling
- Shadcn/ui Documentation - Additional components
- Lucide Icons - Icon library
- Framer Motion Documentation - Animations
- next-themes Documentation - Theme switching
- Vercel Documentation - Deployment platform
- Railway Documentation - Full-stack platform
- Netlify Documentation - Static site hosting
This project is currently using Prisma 5.16.0. This downgrades from the experimental Prisma 7.1.0-beta originally in the template to ensure stability with standard SQLite configuration.
-
Install Dependencies:
npm install
-
Generate Client:
npx prisma generate
-
Setup Database:
npx prisma db push
-
Seed Database:
npx prisma db seed
-
Run Development Server:
npm run dev
The application will be available at http://localhost:3000 (or 3001 if port is busy).