This is a Next.js project bootstrapped with create-next-app.
We provide automated setup scripts to get you started quickly:
./setup.sh.\setup.ps1These scripts will:
- ✅ Automatically install Node.js v24 (reading from
.nvmrc) - ✅ Install
bunpackage manager - ✅ Install all project dependencies
- ✅ Configure PATH variables for the current session
- ✅ Provide clear guidance for persistent PATH configuration
Note: You may need to restart your terminal after the first run for PATH changes to take full effect.
import { auth } from "@/server/auth/config";
import { headers } from "next/headers";
export default async function MyPage() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session?.user) {
redirect("/auth/signin");
}
return <div>Welcome, {session.user.name}!</div>;
}import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
export const myRouter = createTRPCRouter({
// This endpoint requires authentication
myProtectedQuery: protectedProcedure
.query(({ ctx }) => {
// ctx.session.user is guaranteed to exist
return { userId: ctx.session.user.id };
}),
});The easiest way to get started is to use our automated setup scripts:
Linux/macOS/WSL:
./setup.shWindows PowerShell:
.\setup.ps1After setup completes, start the development server:
bun run dev
# or for faster performance with Turbopack:
bunx next dev --turbopackIf you prefer to set up manually or already have Node.js v24 and bun installed:
bun install
bun run devnpm run db:generate- Generate migrations from schema changesnpm run db:push- Push schema changes to databasenpm run db:studio- Open Drizzle Studio (database GUI)
- Define your table in
src/db/schema.ts - Run
npm run db:generateto create a migration - Run
npm run db:pushto apply the migration
This project includes a complete email pipeline for transactional emails (signup verification, password resets, etc.) using Nodemailer, MailHog, and React Email.
-
Start MailHog (Mock SMTP Server)
docker-compose up -d mailhog
-
Configure Environment Variables
Copy
.env.exampleto.envand configure SMTP settings:# For local development with MailHog (default) SMTP_HOST=localhost SMTP_PORT=1025 SMTP_SECURE=false SMTP_FROM_EMAIL=noreply@localhost SMTP_FROM_NAME=Hackathon Template -
Test Email Sending
npm run demo:email
-
View Emails in MailHog
Open your browser to http://localhost:8025 to see captured emails.
Email templates are located in the emails/ directory and use React Email with TailwindCSS.
Start the React Email development server:
npm run email:devThen open http://localhost:3001 to preview templates with live reload.
- Create a new
.tsxfile inemails/directory - Use React Email components and Tailwind utility classes
- Export the component
Example:
import { Html, Button, Container } from "@react-email/components";
export function WelcomeEmail({ userName }: { userName: string }) {
return (
<Html>
<Container>
<h1 className="text-2xl font-bold">Welcome {userName}!</h1>
<Button href="https://example.com" className="bg-blue-600 text-white">
Get Started
</Button>
</Container>
</Html>
);
}Use the emailClient service to send emails from your application:
import { emailClient } from "@/server/email";
import { WelcomeEmail } from "../../../emails/welcome-email";
// Send an email
const result = await emailClient.sendEmail({
to: "user@example.com",
subject: "Welcome to Our App",
reactEmailTemplate: WelcomeEmail({ userName: "John" }),
});
if (result.success) {
console.log("Email sent:", result.messageId);
}Email verification is automatically integrated with better-auth signups. When a user signs up:
- A verification email is sent using the
VerificationEmailtemplate - The email includes a verification link with a token
- Clicking the link verifies the user's email address
The integration is configured in src/server/auth/config.ts:
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
sendVerificationEmail: async ({ user, url, token }) => {
await emailClient.sendEmail({
to: user.email,
subject: "Verify Your Email Address",
reactEmailTemplate: VerificationEmail({
userName: user.name,
verificationUrl: url,
verificationToken: token,
}),
});
},
}The email service is designed to work with any SMTP provider. Update environment variables to switch providers:
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=true
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_FROM_EMAIL=your-email@gmail.comSMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_SECURE=true
SMTP_USER=apikey
SMTP_PASSWORD=your-sendgrid-api-key
SMTP_FROM_EMAIL=noreply@yourdomain.comSMTP_HOST=email-smtp.us-east-1.amazonaws.com
SMTP_PORT=587
SMTP_SECURE=true
SMTP_USER=your-smtp-username
SMTP_PASSWORD=your-smtp-password
SMTP_FROM_EMAIL=noreply@yourdomain.com-
Check if MailHog is running:
docker ps | grep mailhog -
Verify SMTP configuration:
npm run demo:email
-
Check MailHog logs:
docker logs hackathon-mailhog
- Verify environment variables are set correctly
- Check firewall settings if using external SMTP
- For Gmail, ensure "App Passwords" are enabled
- For SendGrid, verify API key permissions
-
Test templates in preview mode:
npm run email:dev
-
Check browser console for errors
-
Verify all React Email components are properly imported
# Start MailHog
docker-compose up -d mailhog
# Stop MailHog
docker-compose down
# View logs
docker logs hackathon-mailhog -f
# Restart MailHog
docker-compose restart mailhogAPI routes are defined using tRPC for end-to-end type safety.
- Create a file in
src/server/api/routers/ - Export your router using
createTRPCRouter - Add it to
src/server/api/root.ts
Example:
// src/server/api/routers/example.ts
import { z } from "zod";
import { createTRPCRouter, publicProcedure, protectedProcedure } from "@/server/api/trpc";
export const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return { greeting: `Hello ${input.text}` };
}),
createPost: protectedProcedure
.input(z.object({ title: z.string() }))
.mutation(({ ctx, input }) => {
// ctx.session.user is available here
return ctx.db.insert(posts).values({
title: input.title,
userId: ctx.session.user.id,
});
}),
});This project includes a robust file upload system with S3-compatible storage integration. Images are uploaded with dimension-aware object keys (no database required).
- Drag-and-drop upload with multi-file support
- Real-time upload progress indicator
- Client-side dimension extraction for fast feedback
- Dimension-aware object keys (
<id>_<width>x<height>.ext) - Gallery display using Next.js
next/imagefor optimal performance - Multiple provider support: MinIO (local), Cloudflare R2, Hetzner S3
- Start MinIO using Docker Compose:
docker-compose up -d-
MinIO Console is available at http://localhost:9001
- Username:
minioadmin - Password:
minioadmin
- Username:
-
The
uploadsbucket is automatically created and configured for public read access.
Add these variables to your .env.local file (see .env.example):
# MinIO (Local Development)
S3_ENDPOINT=http://localhost:9000
S3_REGION=auto
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_BUCKET_NAME=uploads
S3_PUBLIC_URL=http://localhost:9000- Create an R2 bucket in Cloudflare Dashboard
- Generate API tokens with read/write permissions
- Update environment variables:
S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
S3_REGION=auto
S3_ACCESS_KEY_ID=<your-access-key>
S3_SECRET_ACCESS_KEY=<your-secret-key>
S3_BUCKET_NAME=<your-bucket-name>
S3_PUBLIC_URL=https://<custom-domain-or-public-url>Note: For production, configure a custom domain or R2 public URL for the S3_PUBLIC_URL.
- Create a Hetzner Cloud project
- Enable Object Storage and create a bucket
- Generate access credentials
- Update environment variables:
S3_ENDPOINT=https://<region>.your-objectstorage.com
S3_REGION=<region>
S3_ACCESS_KEY_ID=<your-access-key>
S3_SECRET_ACCESS_KEY=<your-secret-key>
S3_BUCKET_NAME=<your-bucket-name>
S3_PUBLIC_URL=https://<region>.your-objectstorage.com- Navigate to
/galleryin your browser - Upload images using drag-and-drop or file picker
- Images appear in the gallery with dimensions parsed from object keys
- All images are rendered using Next.js
next/imagefor optimization
Upload Flow:
- Client selects/drops image files
- Preview is generated client-side
- Client extracts image dimensions
- Server action returns presigned URL
- Client uploads directly to S3
- Public URL is returned to client
Display Flow:
- Gallery page fetches list of objects from S3
- Dimensions are parsed from object keys
- Images are rendered using
next/imagewith proper width/height - No database queries needed - all metadata lives on S3
- Max file size: 10MB
- Allowed types: JPEG, PNG, GIF, WebP
- Filename sanitization: Non-alphanumeric characters replaced with underscores
- Client-side validation: Size/type checks enforced before presign
- ✅ File type validation (client)
- ✅ File size limits enforced (client)
- ✅ Filename sanitization to prevent path traversal
- ✅ Credentials never exposed to client
- ✅ Presigned URLs expire quickly
MinIO Connection Issues:
- Ensure Docker is running:
docker ps - Check MinIO logs:
docker-compose logs minio - Verify bucket exists: Access MinIO Console at http://localhost:9001
Upload Failures:
- Check environment variables are set correctly
- Verify S3 credentials have write permissions
- Ensure bucket name matches configuration
- Check Next.js logs for detailed error messages
Image Display Issues:
- Verify
S3_PUBLIC_URLis accessible from the browser - Check Next.js
remotePatternsconfiguration innext.config.js - Ensure images were uploaded with dimension-aware object keys
Make sure to set these environment variables in your deployment platform:
DATABASE_URL- Your database connection stringDATABASE_AUTH_TOKEN- Database authentication token (for Turso/libSQL)BETTER_AUTH_SECRET- A random secret key (at least 32 characters)BETTER_AUTH_URL- Your production URL (e.g.,https://yourdomain.com)S3_ENDPOINT- S3-compatible endpoint URLS3_REGION- S3 region (use "auto" for R2/MinIO)S3_ACCESS_KEY_ID- S3 access keyS3_SECRET_ACCESS_KEY- S3 secret keyS3_BUCKET_NAME- S3 bucket nameS3_PUBLIC_URL- Public URL for accessing uploaded files (optional, defaults to S3_ENDPOINT)SMTP_HOST- SMTP server hostnameSMTP_PORT- SMTP server port (usually 587 for TLS or 465 for SSL)SMTP_SECURE- Set totruefor SSL,falsefor TLS/STARTTLSSMTP_USER- SMTP username (if required)SMTP_PASSWORD- SMTP password (if required)SMTP_FROM_EMAIL- From email address for outgoing emailsSMTP_FROM_NAME- From name for outgoing emails
- Push your code to GitHub
- Import your repository in Vercel
- Add environment variables
- Deploy!
- Next.js Documentation
- tRPC Documentation
- Drizzle ORM Documentation
- Better Auth Documentation
- Tailwind CSS Documentation
- Dependabot is configured via
.github/dependabot.yml(free on GitHub). - It opens weekly PRs for npm dependencies and GitHub Actions.
- After merging a dependency PR, run
bun installto refreshbun.lockbecause Dependabot does not update Bun lockfiles.