Customization Guide
How to customize and extend Unstack Pro for your product
Customization Guide
Customize Unstack Pro to match your brand and product needs. The template is designed to be extended while keeping the core auth and organization features intact.
You don't need to modify auth code to customize your app. Focus on branding, adding features, and extending the schema.
Branding
Site Configuration
Update your app's name, description, and metadata in /config/site.ts:
export const siteConfig = {
name: "Your App Name",
description: "Your app description for SEO and social sharing",
url: "https://yourdomain.com",
ogImage: "https://yourdomain.com/og.png",
links: {
twitter: "https://twitter.com/yourapp",
github: "https://github.com/yourorg/yourapp",
},
};Logo and Favicon
Replace the default logos:
- Favicon: Replace
/app/favicon.ico - Logo: Add your logo to
/public/logo.pngand/public/logo-dark.png - Update components that reference the logo
import Image from "next/image";
export function Logo() {
return (
<>
<Image
src="/logo.png"
alt="Your App"
width={120}
height={40}
className="dark:hidden"
/>
<Image
src="/logo-dark.png"
alt="Your App"
width={120}
height={40}
className="hidden dark:block"
/>
</>
);
}Colors and Theme
Unstack Pro uses Tailwind CSS v4 with CSS variables for theming. Customize colors in /styles/globals.css:
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5.9% 10%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}Fonts
Customize fonts in /config/fonts.ts:
import { Inter, JetBrains_Mono } from "next/font/google";
export const fontSans = Inter({
subsets: ["latin"],
variable: "--font-sans",
});
export const fontMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-mono",
});Adding OAuth Providers
Better Auth supports many OAuth providers. To add one:
Configure the Provider
Add the provider to your Better Auth server configuration (we use better-convex for the Convex integration):
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// ... existing config
// Add OAuth providers
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
// ... rest of config
});Add Environment Variables
Add OAuth credentials to your environment:
# Google OAuth
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
# GitHub OAuth
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"Don't forget to also set these in Convex:
npx convex env set GOOGLE_CLIENT_ID "your-google-client-id"
npx convex env set GOOGLE_CLIENT_SECRET "your-google-client-secret"Add Sign-In Button
Add the OAuth button to your login page:
"use client";
import { authClient } from "@/lib/auth-client";
import { Button } from "@/components/ui/button";
import { FaGoogle, FaGithub } from "react-icons/fa";
export function OAuthButtons() {
const signInWithGoogle = async () => {
await authClient.signIn.social({
provider: "google",
callbackURL: "/",
});
};
const signInWithGithub = async () => {
await authClient.signIn.social({
provider: "github",
callbackURL: "/",
});
};
return (
<div className="grid gap-2">
<Button variant="outline" onClick={signInWithGoogle}>
<FaGoogle className="mr-2 h-4 w-4" />
Continue with Google
</Button>
<Button variant="outline" onClick={signInWithGithub}>
<FaGithub className="mr-2 h-4 w-4" />
Continue with GitHub
</Button>
</div>
);
}Configure OAuth App
Set up the OAuth app with the provider:
Google:
- Go to Google Cloud Console
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
https://yourdomain.com/api/auth/callback/google
GitHub:
- Go to GitHub Developer Settings
- Create new OAuth App
- Set callback URL:
https://yourdomain.com/api/auth/callback/github
For a full list of supported providers, see the Better Auth Social Providers documentation.
Adding New Pages
Create a New Route
Add pages using Next.js App Router conventions:
import { requireSession } from "@/lib/session";
export default async function DashboardPage() {
const session = await requireSession();
return (
<div className="container py-8">
<h1 className="text-3xl font-bold">Dashboard</h1>
<p>Welcome, {session.user.name}!</p>
</div>
);
}Protected Routes
Use requireSession() to protect routes - it automatically redirects to the login page if the user is not authenticated:
import { requireSession } from "@/lib/session";
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
await requireSession();
return <>{children}</>;
}Organization-Scoped Routes
Access organization context in routes:
import { requireSession } from "@/lib/session";
interface Props {
params: { organizationSlug: string };
}
export default async function ProjectsPage({ params }: Props) {
const session = await requireSession();
const { organizationSlug } = params;
// Fetch organization-specific data
// Verify user has access to this organization
return (
<div>
<h1>Projects for {organizationSlug}</h1>
{/* Your content */}
</div>
);
}Customizing Auth Flows
Custom Email Templates
Modify email templates in /emails/:
import { Body, Container, Heading, Html, Text } from "@react-email/components";
export default function VerificationEmail({ otp }: { otp: string }) {
return (
<Html>
<Body style={{ fontFamily: "sans-serif" }}>
<Container>
<Heading>Welcome to Your App!</Heading>
<Text>Your verification code is: <strong>{otp}</strong></Text>
<Text>This code expires in 10 minutes.</Text>
</Container>
</Body>
</Html>
);
}Custom Validation Rules
Extend Zod schemas for custom validation:
import { z } from "zod";
export const registerSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Password must contain an uppercase letter")
.regex(/[0-9]/, "Password must contain a number"),
});
export const customProfileSchema = z.object({
name: z.string().min(2),
company: z.string().optional(),
jobTitle: z.string().optional(),
website: z.string().url().optional(),
});Removing Optional Features
Remove Autumn (Billing)
If you don't need billing:
- Remove
AUTUMN_API_KEYfrom environment variables - Delete billing-related routes in
/app/organizations/[organizationSlug]/billing/ - Remove billing UI components
- Update organization creation to not require billing
Remove Sentry (Error Monitoring)
If you don't need error tracking:
- Delete
instrumentation.ts - Delete
sentry.edge.config.ts - Delete
sentry.server.config.ts - Remove Sentry configuration from
next.config.js - Uninstall Sentry packages:
npm uninstall @sentry/nextjsRemove Passkeys
If you don't need passkey authentication:
- Remove passkey routes in
/app/account/security/passkeys/ - Remove passkey components
- Disable passkey plugin in Better Auth config:
export const auth = betterAuth({
// Remove or comment out passkey plugin
// plugins: [passkey()],
});Adding New Components
Create Reusable Components
Add to /components/ for shared components:
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
interface StatsCardProps {
title: string;
value: string | number;
description?: string;
icon?: React.ReactNode;
}
export function StatsCard({ title, value, description, icon }: StatsCardProps) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{title}</CardTitle>
{icon}
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
{description && (
<p className="text-xs text-muted-foreground">{description}</p>
)}
</CardContent>
</Card>
);
}Use Shadcn UI Components
Add new Shadcn components as needed:
npx shadcn@latest add chart
npx shadcn@latest add calendar
npx shadcn@latest add commandCustom Hooks
Add custom hooks in /lib/hooks/:
"use client";
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
export function useOrganization(slug: string) {
const organization = useQuery(api.organizations.getBySlug, { slug });
const members = useQuery(api.organizations.getMembers, { slug });
return {
organization,
members,
isLoading: organization === undefined,
};
}Environment-Specific Configuration
Development vs Production
Use environment checks for different behavior:
const isDev = process.env.NODE_ENV === "development";
export const config = {
apiUrl: isDev
? "http://localhost:3000/api"
: "https://yourdomain.com/api",
enableDebugLogs: isDev,
mockPayments: isDev,
};Best Practices
- Don't modify core auth files unless necessary - extend instead
- Keep components small and focused on one responsibility
- Use TypeScript strictly for type safety
- Follow existing patterns for consistency
- Test changes locally before deploying
- Document custom code for future reference
- Use environment variables for all configuration
- Keep secrets secure - never commit them
Resources
- Next.js Documentation
- Convex Documentation
- better-convex Documentation - Our Better Auth + Convex integration
- Better Auth Documentation
- Tailwind CSS Documentation
- Shadcn UI Documentation