A minimal AI chat application built with Next.js 15, Vercel AI SDK, and Google authentication. Switch between OpenAI (GPT-5) and Anthropic (Claude Opus 4.5) models. You are not required to use it, but it might prove helpful.
We expect you to use Claude Code or similar AI tools to work with this example. If you need a refresher on Claude Code, check out the workshop slides from Flo's workshop last week. If you need help setting up AI tools, talk to us.
- Framework: Next.js 15 (App Router)
- AI SDK: Vercel AI SDK 4.0
- Models: OpenAI GPT-5, Anthropic Claude Opus 4.5
- Auth: NextAuth.js v5 with Google OAuth
- Language: TypeScript
npm installNote:
- You can get an Anthropic API key from the Claude Console using the email you use to sign into Claude Code
- If you have access to an OpenAI account, you can get an API key from their console
- For the Google login credentials, talk to Flo and Stephan - they can provide you with the credentials required and can add any required domains/origins to GCP
With the credentials in hand, create a .env file in the project root:
# AI API Keys
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# NextAuth Configuration
AUTH_SECRET=your-random-secret-here
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
# Required for Docker: set to your actual access URL
AUTH_URL=http://localhost:3001
NEXTAUTH_URL=http://localhost:3001
AUTH_TRUST_HOST=trueTip: Generate AUTH_SECRET with:
openssl rand -base64 32Note: Google Sign-in can be hard to set up. If you run into hard-to-fix issues, feel free to deviate to another authentication method (or disable auth entirely). We're more interested in seeing your project than checking if you can implement Oauth under pressure.
- Go to Google Cloud Console
- Create a new project or select an existing one
- Navigate to APIs & Services → OAuth consent screen
- Select Internal (for Workspace) or External
- Fill in App name, User support email, and Developer contact
- Navigate to APIs & Services → Credentials
- Click Create Credentials → OAuth client ID
- Select Web application
- Add Authorized JavaScript origins:
http://localhost:3000 http://localhost:3001 (Railway URL if you have one) - Add Authorized redirect URIs:
http://localhost:3000/api/auth/callback/google http://localhost:3001/api/auth/callback/google (Railway URL if you have one) - Copy the Client ID and Client Secret to your
.envfile
npm run devOpen http://localhost:3000 in your browser.
# Build the image
docker build -t meingpt-hackathon .
# Run the container (note: -p 3001:3000 maps external 3001 to internal 3000)
docker run -p 3001:3000 --env-file .env \
-e AUTH_URL=http://localhost:3001 \
-e NEXTAUTH_URL=http://localhost:3001 \
-e AUTH_TRUST_HOST=true \
meingpt-hackathonOpen http://localhost:3001 in your browser.
Important: The AUTH_URL and NEXTAUTH_URL must match the URL you access in your browser. When using Docker with port mapping (-p 3001:3000), set these to http://localhost:3001. When deploying using Railway, use the generated URL.
├── app/
│ ├── api/
│ │ ├── auth/[...nextauth]/route.ts # Auth API routes
│ │ └── chat/route.ts # AI chat endpoint
│ ├── login/page.tsx # Login page
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Main chat interface
│ └── globals.css # Global styles
├── auth.ts # NextAuth configuration
├── middleware.ts # Auth middleware
├── Dockerfile # Docker configuration
└── package.json
- Google Sign-In: Secure authentication via Google OAuth
- Model Switching: Toggle between GPT-5 and Claude Opus 4.5 mid-conversation
- Streaming Responses: Real-time AI response streaming
- Protected Routes: Automatic redirect to login for unauthenticated users
Edit app/api/chat/route.ts to add more models:
const models = {
"gpt-5": openai("gpt-5"),
"gpt-4o": openai("gpt-4o"),
"claude-opus": anthropic("claude-sonnet-4-5"),
"claude-sonnet": anthropic("claude-sonnet-4-20250514"),
};const result = streamText({
model: selectedModel,
system: "You are a helpful assistant for the meinGPT hackathon...",
messages,
});import { tool } from "ai";
import { z } from "zod";
const result = streamText({
model: selectedModel,
messages,
tools: {
getWeather: tool({
description: "Get current weather",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => ({ temp: 20, city }),
}),
},
});This usually means Auth.js can't determine the correct host. Ensure:
AUTH_URLandNEXTAUTH_URLare set to your actual browser URLAUTH_TRUST_HOST=trueis set when running in Docker
- Verify your OAuth client type is Web application
- Check that both redirect URIs are added (
:3000and:3001) - For Workspace: ensure the app is trusted in Admin Console
- Wait a few minutes after making changes in Google Cloud Console
Happy Hacking! 🚀