Use when deploying to Vercel - covers vercel.json configuration, environment variables (printf gotcha), monorepo setup, Next.js issues, preview deployments, and common build errors
Handles Vercel deployments including vercel.json configuration, environment variables (critical printf gotcha), monorepo setup, Next.js build errors, and preview deployments. Use when configuring deployments, troubleshooting build failures, or managing environment variables for Vercel projects.
/plugin marketplace add SecurityRonin/ronin-marketplace/plugin install deployment-skills@ronin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
vercel.json for deployments{
"$schema": "https://openapi.vercel.sh/vercel.json"
}
Most projects don't need vercel.json - Vercel auto-detects frameworks.
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1"],
"functions": {
"api/**/*.ts": {
"memory": 1024,
"maxDuration": 30
}
}
}
{
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true }
],
"rewrites": [
{ "source": "/api/:path*", "destination": "https://api.example.com/:path*" }
],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" }
]
}
]
}
echo adds a trailing newline that becomes part of the value, breaking API keys and secrets.
</EXTREMELY-IMPORTANT>
# ❌ WRONG - echo adds trailing newline
echo "sk-abc123" | vercel env add SECRET_KEY production
# ✅ CORRECT - printf has no trailing newline
printf "sk-abc123" | vercel env add SECRET_KEY production
# ✅ ALSO CORRECT - interactive prompt
vercel env add SECRET_KEY production
# Then paste value when prompted
Symptoms of trailing newline bug:
Diagnosis:
# Check for trailing newline
vercel env pull .env.local
cat -A .env.local | grep SECRET_KEY
# If you see SECRET_KEY=sk-abc123$ - no newline (good)
# If you see SECRET_KEY=sk-abc123\n$ - has newline (bad)
| Type | When Used | Example |
|---|---|---|
production | Production deployments only | API keys, database URLs |
preview | Preview deployments (PRs, branches) | Staging API keys |
development | Local dev via vercel dev | Local overrides |
# Add to production only
vercel env add API_KEY production
# Add to all environments
vercel env add API_KEY production preview development
# Pull to local .env
vercel env pull .env.local
| Framework | Public Prefix | Private (server-only) |
|---|---|---|
| Next.js | NEXT_PUBLIC_* | No prefix |
| Vite | VITE_* | No prefix |
| Create React App | REACT_APP_* | No prefix |
# Client-accessible (bundled into JS)
vercel env add NEXT_PUBLIC_API_URL production
# Server-only (API routes, SSR)
vercel env add DATABASE_URL production
// vercel.json at repo root
{
"buildCommand": "cd apps/web && npm run build",
"outputDirectory": "apps/web/dist",
"installCommand": "npm install",
"rootDirectory": "apps/web"
}
Create separate Vercel projects, each with different rootDirectory:
Project 1 (Web App):
{
"rootDirectory": "apps/web"
}
Project 2 (API):
{
"rootDirectory": "apps/api"
}
{
"buildCommand": "cd ../.. && pnpm turbo build --filter=web",
"outputDirectory": ".next",
"rootDirectory": "apps/web"
}
Common issue: Build fails because dependencies aren't installed.
Fix: Set install command at root level:
{
"installCommand": "cd ../.. && pnpm install",
"buildCommand": "cd ../.. && pnpm turbo build --filter=web",
"rootDirectory": "apps/web"
}
Error: useX must be used within Provider
Error: useAuth must be used within an AuthProvider
Error occurred prerendering page "/dashboard"
Cause: Next.js tries to statically prerender pages that use React Context.
Fix: Wrap providers in layout.tsx:
// src/components/Providers.tsx
'use client'
export default function Providers({ children }) {
return <AuthProvider>{children}</AuthProvider>
}
// src/app/layout.tsx
import Providers from '../components/Providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
Error: NEXT_PUBLIC_* undefined at build time
Cause: Environment variable not set in Vercel project settings.
Fix:
vercel env add NEXT_PUBLIC_API_URL productionNEXT_PUBLIC_*)Error: Dynamic server usage / opted out of static rendering
Error: Dynamic server usage: cookies
Route /dashboard couldn't be rendered statically
Cause: Using dynamic functions (cookies(), headers()) in pages Vercel tries to statically generate.
Fix: Export dynamic route config:
// Force dynamic rendering
export const dynamic = 'force-dynamic'
// Or force static
export const dynamic = 'force-static'
// next.config.js
module.exports = {
output: 'standalone', // For Docker/self-hosting
// output: 'export', // Static HTML export (no server features)
}
Vercel auto-detects - usually don't need to set this.
Every push to a non-production branch creates a preview deployment:
feature-branch → project-feature-branch-xxx.vercel.appPreview deployments use preview environment variables:
# Production database
vercel env add DATABASE_URL production
# Value: postgres://prod-db...
# Preview/staging database
vercel env add DATABASE_URL preview
# Value: postgres://staging-db...
// vercel.json
{
"git": {
"deploymentEnabled": {
"main": true,
"feature/*": false
}
}
}
npm install -g vercel
vercel login
vercel whoami
# Deploy to preview
vercel
# Deploy to production
vercel --prod
# Deploy without prompts
vercel --prod --yes
# Deploy specific directory
vercel ./dist --prod
# Link local project to Vercel
vercel link
# List deployments
vercel list
# View deployment logs
vercel logs <deployment-url>
# Inspect deployment
vercel inspect <deployment-url>
# Promote deployment to production
vercel promote <deployment-url>
# List env vars
vercel env ls
# Add env var
vercel env add VAR_NAME production
# Remove env var
vercel env rm VAR_NAME production
# Pull env vars to local file
vercel env pull .env.local
# List recent deployments
vercel list
# Promote previous deployment to production
vercel promote <previous-deployment-url>
# Or instant rollback in dashboard
# Deployments → ... → Promote to Production
// app/api/hello/route.ts (App Router)
export async function GET(request: Request) {
return Response.json({ message: 'Hello' })
}
// pages/api/hello.ts (Pages Router)
export default function handler(req, res) {
res.status(200).json({ message: 'Hello' })
}
// api/hello.ts
import type { VercelRequest, VercelResponse } from '@vercel/node'
export default function handler(req: VercelRequest, res: VercelResponse) {
res.status(200).json({ message: 'Hello' })
}
// vercel.json
{
"functions": {
"api/**/*.ts": {
"memory": 1024, // MB (128-3008)
"maxDuration": 30 // seconds (max 300 on Pro)
}
}
}
// app/api/edge/route.ts
export const runtime = 'edge'
export async function GET(request: Request) {
return new Response('Hello from the edge!')
}
Edge vs Serverless:
| Feature | Edge | Serverless |
|---|---|---|
| Cold start | ~0ms | 250-500ms |
| Memory | 128MB | Up to 3GB |
| Duration | 30s | Up to 300s |
| Node.js APIs | Limited | Full |
| Location | All regions | Selected region |
# Link to Vercel remote cache
npx turbo login
npx turbo link
// turbo.json
{
"remoteCache": {
"signature": true
}
}
If builds are stale or broken:
vercel --forceError: Build exceeded maximum duration
Fixes:
turbo prune for monoreposError: FATAL ERROR: JavaScript heap out of memory
Fix: Increase Node memory in build command:
{
"buildCommand": "NODE_OPTIONS='--max-old-space-size=4096' npm run build"
}
Error: Cannot find module 'x'
Causes:
devDependencies but needed at runtimepackage.jsonFix: Move to dependencies or check case sensitivity.
If your domain redirects (e.g., example.com → www.example.com), the OAuth callback URL must use the final destination domain:
https://www.example.com/api/auth/callback ✓
https://example.com/api/auth/callback ✗ (if it redirects to www)
# Check redirect URL for corruption
curl -s -I "https://your-app.com/api/auth/github" | grep location
Look for %0A (newline) or unexpected characters in client_id - indicates env var has trailing newline.
Common errors:
client_id and/or client_secret passed are incorrect → Check for newlines in env vars404 on callback → Callback URL mismatch in OAuth app settingsVercel OIDC tokens have aud: "https://vercel.com/{team-slug}" but GCP providers often default to expecting https://oidc.vercel.com/{team-slug}.
Diagnosis:
gcloud iam workload-identity-pools providers describe {provider} \
--location=global \
--workload-identity-pool={pool} \
--project={project} \
--format="value(oidc.allowedAudiences)"
Fix: Update allowed audience to match Vercel's token:
gcloud iam workload-identity-pools providers update-oidc {provider} \
--location=global \
--workload-identity-pool={pool} \
--project={project} \
--allowed-audiences="https://vercel.com/{team-slug}"
Use @vercel/functions/oidc, NOT the deprecated @vercel/oidc:
// ❌ Old (deprecated, causes "getToken is not a function")
import { getToken } from '@vercel/oidc'
// ✅ New
import { getVercelOidcToken } from '@vercel/functions/oidc'
If env vars have trailing newlines, the STS audience string becomes corrupted:
"//iam.googleapis.com/projects/123456\n/locations/global..."
Symptoms:
\n in the stsAudience fieldFix: Re-add each WIF env var using printf (not dashboard copy-paste):
printf "value" | vercel env add GCP_PROJECT_NUMBER production --force
printf "value" | vercel env add GCP_WORKLOAD_IDENTITY_POOL_ID production --force
printf "value" | vercel env add GCP_WORKLOAD_IDENTITY_PROVIDER_ID production --force
printf "value" | vercel env add GCP_SERVICE_ACCOUNT_EMAIL production --force
vercel domains add example.com
| Type | Name | Value |
|---|---|---|
| A | @ | 76.76.21.21 |
| CNAME | www | cname.vercel-dns.com |
Automatic via Let's Encrypt. No configuration needed.
# Deploy to production
vercel --prod
# Add env var (use printf!)
printf "value" | vercel env add KEY production
# Pull env vars locally
vercel env pull .env.local
# View logs
vercel logs <url>
# Rollback
vercel promote <previous-url>
# Clear cache and redeploy
vercel --force --prod
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.