A minimal, production-ready starter template with authentication, real-time database, and UI components.
- Next.js 16 - React framework with App Router
- Convex - Real-time database and backend
- Stack Auth - Authentication (OAuth, email, magic links)
- shadcn/ui - UI components with Radix UI and Tailwind CSS
- TypeScript - Type safety
- Tailwind CSS - Styling
├── app/ # Next.js app directory
│ ├── handler/ # Stack Auth pages
│ ├── layout.tsx # Root layout with providers
│ └── page.tsx # Home page
├── components/
│ ├── ConvexClientProvider.tsx # Convex + Auth integration
│ └── ui/ # shadcn/ui components
├── convex/ # Backend code
│ ├── auth.config.ts # Auth configuration
│ ├── myFunctions.ts # Example queries/mutations
│ └── schema.ts # Database schema
├── stack/ # Stack Auth config
│ ├── client.tsx
│ └── server.tsx
└── lib/ # Utilities
git clone https://github.com/Developing-Gamer/next-convex-stack-template.git
cd next-convex-stack-template
npm installcp .env.example .env.local- Go to app.stack-auth.com and create a project
- Copy credentials to
.env.local:
NEXT_PUBLIC_STACK_PROJECT_ID=your_project_id
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=your_key
STACK_SECRET_SERVER_KEY=your_secretnpx convex devThis will:
- Prompt you to log in with GitHub
- Create a Convex project
- Add
CONVEX_DEPLOYMENTandNEXT_PUBLIC_CONVEX_URLto.env.local
Important: Add Stack Auth env vars to Convex:
- Open Convex Dashboard
- Go to Settings → Environment Variables
- Add the 3 Stack Auth variables from
.env.local
npm run devOpen localhost:3000
// Client Component - Check auth
"use client";
import { useUser } from "@stackframe/stack";
export default function Profile() {
const user = useUser();
return user ? <div>Hello {user.displayName}</div> : <div>Sign in</div>;
}// Server Component - Protect page
import { stackServerApp } from "@/stack/server";
import { redirect } from "next/navigation";
export default async function Dashboard() {
const user = await stackServerApp.getUser();
if (!user) redirect("/handler/sign-in");
return <div>Dashboard</div>;
}// convex/schema.ts - Define schema
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
tasks: defineTable({
text: v.string(),
userId: v.string(),
}).index("by_user", ["userId"]),
});// convex/tasks.ts - Create functions
import { v } from "convex/values";
import { query, mutation } from "./_generated/server";
import { stackServerApp } from "@stackframe/stack";
export const getTasks = query({
handler: async (ctx) => {
const user = await stackServerApp.getPartialUser({ from: "convex", ctx });
if (!user) return [];
return await ctx.db
.query("tasks")
.filter((q) => q.eq(q.field("userId"), user.id))
.collect();
},
});
export const addTask = mutation({
args: { text: v.string() },
handler: async (ctx, args) => {
const user = await stackServerApp.getPartialUser({ from: "convex", ctx });
if (!user) throw new Error("Not authenticated");
await ctx.db.insert("tasks", { text: args.text, userId: user.id });
},
});// Use in component
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
export default function Tasks() {
const tasks = useQuery(api.tasks.getTasks);
const addTask = useMutation(api.tasks.addTask);
return (
<div>
{tasks?.map((task) => (
<div key={task._id}>{task.text}</div>
))}
<button onClick={() => addTask({ text: "New task" })}>Add</button>
</div>
);
}import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function Example() {
return (
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
</CardHeader>
<CardContent>
<Button>Click me</Button>
</CardContent>
</Card>
);
}Add more components:
npx shadcn@latest add table
npx shadcn@latest add form- Push code to GitHub
- Deploy Convex:
npx convex deploy - Import to Vercel
- Add environment variables (use production Convex URL)
- Update Stack Auth dashboard with production domain
npm run dev # Run frontend + backend
npm run dev:frontend # Next.js only
npm run dev:backend # Convex only
npm run build # Build for production
npm run lint # Lint code
npx convex deploy # Deploy backendMIT