- Prevents lefthook from failing in CI/Vercel environments - Only installs git hooks when in actual git repository 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
26 KiB
API Routes Timeout Issue - Complete Investigation Report
Problem Statement
API routes deployed to Vercel are timing out with no response. The Next.js application pages work perfectly, but all API endpoints at /api/* return timeouts or "Redirecting..." messages.
Affected URLs:
https://tpmjs.com/api/health- Returns "Redirecting..."https://tpmjs.com/api/tools- Returns "Redirecting..."https://tpmjs-1chh44d1u-tpmjs.vercel.app/api/health- Timeouts (exit code 28)https://tpmjs-1chh44d1u-tpmjs.vercel.app/api/tools- Timeouts (exit code 28)
Working:
- All page routes work correctly (e.g.,
/,/tool/[slug]) - UI navigation and client-side routing function normally
- Local development API routes work perfectly
Environment Details
Project Structure
- Monorepo: Turborepo setup with pnpm workspaces
- Framework: Next.js 16.0.4 (App Router)
- Node Version: 24.x (on Vercel)
- Deployment Platform: Vercel
- Custom Domains: tpmjs.com, www.tpmjs.com
Repository Structure
tpmjs/
├── apps/
│ └── web/ # Next.js 16 App Router application
│ ├── src/
│ │ └── app/
│ │ ├── api/
│ │ │ ├── health/route.ts
│ │ │ ├── stats/route.ts
│ │ │ ├── tools/
│ │ │ │ ├── route.ts
│ │ │ │ ├── [id]/route.ts
│ │ │ │ ├── [slug]/route.ts
│ │ │ │ └── validate/route.ts
│ │ │ └── sync/
│ │ │ ├── changes/route.ts
│ │ │ ├── keyword/route.ts
│ │ │ └── metrics/route.ts
│ │ ├── page.tsx
│ │ └── tool/[slug]/page.tsx
│ ├── next.config.ts
│ └── vercel.json
├── packages/
│ ├── db/ # Prisma client
│ ├── types/ # Shared TypeScript types
│ ├── utils/ # Utility functions
│ ├── env/ # Environment validation
│ └── ui/ # React component library
├── vercel.json # Root Vercel configuration
└── turbo.json
API Route Examples
/apps/web/src/app/api/health/route.ts
import { NextResponse } from 'next/server';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 60;
/**
* GET /api/health
* Simple health check endpoint that doesn't touch the database
*/
export async function GET() {
return NextResponse.json({
status: 'ok',
timestamp: new Date().toISOString(),
env: {
hasDatabase: !!process.env.DATABASE_URL,
nodeEnv: process.env.NODE_ENV,
},
});
}
/apps/web/src/app/api/tools/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@tpmjs/db/client';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export async function GET(request: Request) {
// ... query string parsing
const [tools, totalCount] = await Promise.all([
prisma.tool.findMany({
where,
orderBy: [
{ qualityScore: 'desc' },
{ npmDownloadsLastMonth: 'desc' },
{ createdAt: 'desc' },
],
take: limit,
skip: offset,
}),
prisma.tool.count({ where }),
]);
return NextResponse.json({
data: tools,
pagination: {
page,
limit,
total: totalCount,
totalPages: Math.ceil(totalCount / limit),
},
});
}
Configuration Files
/apps/web/next.config.ts (Current)
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
transpilePackages: ['@tpmjs/ui', '@tpmjs/utils', '@tpmjs/db', '@tpmjs/types', '@tpmjs/env'],
reactStrictMode: true,
};
export default nextConfig;
/apps/web/vercel.json (Current)
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "cd ../.. && pnpm --filter=@tpmjs/web build",
"installCommand": "pnpm install"
}
/vercel.json (Root)
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"git": {
"deploymentEnabled": {
"main": true
}
},
"github": {
"silent": false,
"autoJobCancelation": true
},
"crons": [
{
"path": "/api/sync/changes",
"schedule": "*/2 * * * *"
},
{
"path": "/api/sync/keyword",
"schedule": "*/15 * * * *"
},
{
"path": "/api/sync/metrics",
"schedule": "0 * * * *"
}
]
}
Local Build Verification
Local Build Output Structure
$ ls -R /Users/ajaxdavis/repos/tpmjs/tpmjs/apps/web/.next/server/app/api/
health/
stats/
sync/
tools/
/apps/web/.next/server/app/api/health:
route
route.js
route.js.map
route.js.nft.json
route_client-reference-manifest.js
/apps/web/.next/server/app/api/stats:
route
route.js
route.js.map
route.js.nft.json
route_client-reference-manifest.js
/apps/web/.next/server/app/api/tools:
[id]/
[slug]/
validate/
route
route.js
route.js.map
route.js.nft.json
route_client-reference-manifest.js
Routes Manifest Confirmation
$ cat /apps/web/.next/routes-manifest.json | jq '.staticRoutes[] | select(.page | contains("api"))'
{
"page": "/api/health",
"regex": "^/api/health(?:/)?$",
"routeKeys": {},
"namedRegex": "^/api/health(?:/)?$"
}
{
"page": "/api/stats",
"regex": "^/api/stats(?:/)?$",
"routeKeys": {},
"namedRegex": "^/api/stats(?:/)?$"
}
{
"page": "/api/sync/changes",
"regex": "^/api/sync/changes(?:/)?$",
"routeKeys": {},
"namedRegex": "^/api/sync/changes(?:/)?$"
}
{
"page": "/api/sync/keyword",
"regex": "^/api/sync/keyword(?:/)?$",
"routeKeys": {},
"namedRegex": "^/api/sync/keyword(?:/)?$"
}
{
"page": "/api/sync/metrics",
"regex": "^/api/sync/metrics(?:/)?$",
"routeKeys": {},
"namedRegex": "^/api/sync/metrics(?:/)?$"
}
Node File Trace (NFT) Verification
$ cat /apps/web/.next/server/app/api/health/route.js.nft.json
{
"version": 1,
"files": [
"../../../../../../../node_modules/.pnpm/next@16.0.4_@babel+core@7.28.5_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/next/dist/client/components/app-router-headers.js",
"../../../../../../../node_modules/.pnpm/next@16.0.4_@babel+core@7.28.5_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/next/dist/compiled/@opentelemetry/api/index.js",
// ... many more dependencies
]
}
Conclusion: API routes build correctly locally with all dependencies properly traced.
Vercel Deployment Analysis
Deployment Inspection Output
$ vercel inspect https://tpmjs-1chh44d1u-tpmjs.vercel.app
General
id dpl_2FHyiTWtBZzvT8EcohwYEZmdb8rb
name tpmjs-web
target production
status ● Ready
url https://tpmjs-1chh44d1u-tpmjs.vercel.app
created Fri Nov 28 2025 20:44:03 GMT+1000
Aliases
╶ https://www.tpmjs.com
╶ https://tpmjs-web.vercel.app
╶ https://tpmjs-web-tpmjs.vercel.app
╶ https://tpmjs-web-git-main-tpmjs.vercel.app
╶ https://tpmjs.com
Builds
┌ . [0ms]
├── λ tool/[slug] (562.92KB) [iad1]
├── λ tool/[slug].rsc (562.92KB) [iad1]
├── λ _global-error (642.48KB) [iad1]
├── λ _global-error.rsc (642.48KB) [iad1]
├── λ _global-error.segments/__PAGE__.segment.rsc (642.48KB) [iad1]
└── 56 output items hidden
CRITICAL FINDING: No API routes are listed in the build output. Only pages (tool/[slug], _global-error, etc.) appear as serverless functions (λ).
Expected API routes that should appear:
λ api/healthλ api/toolsλ api/tools/[id]λ api/tools/[slug]λ api/sync/changes- etc.
Testing Results
# Direct Vercel URL - Timeouts
$ curl -s -m 10 https://tpmjs-1chh44d1u-tpmjs.vercel.app/api/health
# Exit code 28 (timeout)
# Custom Domain - Returns "Redirecting..."
$ curl -s -m 10 https://tpmjs.com/api/health
Redirecting...
# Custom Domain - Returns "Redirecting..."
$ curl -s -m 10 https://tpmjs.com/api/tools
Redirecting...
# Check redirect headers
$ curl -I https://tpmjs.com
HTTP/2 307
cache-control: public, max-age=0, must-revalidate
content-type: text/plain
date: Fri, 28 Nov 2025 10:38:12 GMT
location: https://www.tpmjs.com/
server: Vercel
Vercel Configuration Details
User-Confirmed Settings
- Root Directory:
apps/web(set in Vercel dashboard) - DATABASE_URL: Configured in Vercel environment variables (Production)
- Framework Preset: (Unknown - needs verification)
- Build Output Directory: (Unknown - using default
.next)
Project List
$ vercel project ls | grep -i tpmjs
tpmjs -- 16h 24.x
v0-tool-registry-page https://tpmjs.com 3d 22.x
NOTE: Two projects exist:
tpmjs- Current project (Node 24.x)v0-tool-registry-page- Also has tpmjs.com domain (Node 22.x)
This could indicate a domain routing conflict or outdated project.
Investigation Timeline & Attempts
Attempt 1: Remove Root-Level Redirects
Hypothesis: The redirect in /vercel.json was intercepting API requests.
Original /vercel.json:
{
"redirects": [
{
"source": "/:path*",
"has": [
{
"type": "host",
"value": "www.tpmjs.com"
}
],
"destination": "https://tpmjs.com/:path*",
"permanent": true
}
]
}
Action: Removed the redirects array from root /vercel.json.
Result: ❌ API routes still timeout. Redirect rule was not the root cause.
Commit: 9be3af7 fix(routing): move www redirect from vercel.json to Next.js config
Attempt 2: Move Redirects to Next.js Config
Hypothesis: Next.js should handle redirects after routing.
Action: Added async redirects() to apps/web/next.config.ts:
async redirects() {
return [
{
source: '/:path*',
has: [
{
type: 'host',
value: 'www.tpmjs.com',
},
],
destination: 'https://tpmjs.com/:path*',
permanent: true,
},
];
}
Result: ❌ API routes returned "Redirecting..." instead of executing. Next.js async redirects() applies to ALL routes including API routes.
Commit: 9be3af7 fix(routing): move www redirect from vercel.json to Next.js config
Attempt 3: Exclude API Routes from Redirect
Hypothesis: Use regex to exclude /api/* from redirects.
Action: Modified redirect pattern:
async redirects() {
return [
{
source: '/((?!api).*)', // Negative lookahead to exclude /api/*
has: [
{
type: 'host',
value: 'www.tpmjs.com',
},
],
destination: 'https://tpmjs.com/$1',
permanent: true,
},
];
}
Result: ❌ API routes back to timing out (not redirecting anymore, but still not working).
Commit: c42bfb6 fix(redirects): exclude API routes from www redirect
Attempt 4: Remove All Redirects
Hypothesis: Eliminate redirect loop causing ERR_TOO_MANY_REDIRECTS.
Action: Removed async redirects() entirely from next.config.ts.
Result: ✅ Redirect loop fixed. ❌ API routes still timeout.
Commit: b93cd42 fix: remove redirects to resolve redirect loop
Attempt 5: Add Vercel Functions Configuration
Hypothesis: Vercel needs explicit configuration to detect API routes.
Action: Added to apps/web/vercel.json:
{
"functions": {
"app/api/**/*.ts": {
"maxDuration": 60
}
}
}
Result: ❌ No change. API routes still timeout.
Commit: 748ca4d fix(api): configure Vercel functions for API routes with maxDuration
Attempt 6: Add maxDuration to Route Files
Hypothesis: Export configuration directly in route handlers.
Action: Added to apps/web/src/app/api/health/route.ts:
export const maxDuration = 60;
Result: ❌ No change. API routes still timeout.
Commit: 8281f8f fix(build): disable Turbopack for Vercel deployment
Attempt 7: Disable Turbopack
Hypothesis: Turbopack (Next.js 16 default) has compatibility issues with Vercel.
Action: Added --webpack flag to build command:
{
"buildCommand": "cd ../.. && turbo build --filter=@tpmjs/web -- --webpack"
}
Result: ❌ Build failed completely. Invalid flag syntax.
Commit: 8281f8f fix(build): disable Turbopack for Vercel deployment
Attempt 8: Simplify Build Command
Hypothesis: Use direct pnpm build instead of Turbo wrapper.
Action: Changed to:
{
"buildCommand": "cd ../.. && pnpm --filter=@tpmjs/web build"
}
Result: ⏳ Pending deployment test.
Commit: 065196d fix(build): simplify Vercel build command
Root Cause Analysis
What We Know FOR SURE
-
✅ API routes build correctly locally
- All 9 API routes compile to
.next/server/app/api/ - NFT (Node File Trace) files are generated with proper dependencies
- Routes manifest includes all API routes
- All 9 API routes compile to
-
✅ Next.js configuration is correct
export const runtime = 'nodejs'set correctlyexport const dynamic = 'force-dynamic'set correctlytranspilePackagesincludes all workspace packages
-
✅ Pages deploy and work perfectly
/tool/[slug]renders correctly- Homepage loads
- Client-side navigation works
-
❌ API routes are NOT deployed as serverless functions
vercel inspectshows NO API routes in build output- Only pages appear as
λ(lambda) functions - This is the PRIMARY issue
-
❌ Direct Vercel URLs timeout
- Not just a custom domain issue
- Affects
*.vercel.appURLs - Exit code 28 (timeout) - no response at all
-
❌ Custom domain shows "Redirecting..."
- Even with all redirects removed from config
- Suggests a redirect at Vercel platform level OR DNS level
- Could be from the
v0-tool-registry-pageproject conflict
Possible Root Causes
Theory 1: Vercel Project Misconfiguration
Likelihood: HIGH
Evidence:
- Two projects with same domain (
tpmjsandv0-tool-registry-page) - Framework Preset might not be set to "Next.js"
- Root Directory is
apps/webbut Vercel might not be detecting Next.js properly
What to Check:
-
Vercel Dashboard → Project Settings → General
- Framework Preset: Should be "Next.js"
- Root Directory: Should be "apps/web"
- Build Command: Should match vercel.json
- Output Directory: Should be blank (default
.next)
-
Vercel Dashboard → Domains
- Check if both projects have tpmjs.com
- Remove domain from
v0-tool-registry-pageif present
-
Vercel Dashboard → Deployments → Build Logs
- Search for "API" or "route"
- Look for errors about missing functions
- Check if Next.js is detected correctly
Theory 2: Monorepo Detection Issue
Likelihood: MEDIUM
Evidence:
- Build command uses
cd ../.. && pnpm --filter=@tpmjs/web build - Vercel might not be correctly detecting workspace structure
transpilePackagesincludes workspace packages
What to Check:
- Build logs for workspace resolution errors
- Check if
node_modulesis being created in correct location - Verify pnpm workspace configuration
Potential Fix:
Try setting installCommand to:
{
"installCommand": "pnpm install --shamefully-hoist"
}
Theory 3: Next.js 16 + Vercel Incompatibility
Likelihood: MEDIUM
Evidence:
- Next.js 16 released recently (November 2024)
- Turbopack is default (might have Vercel issues)
- App Router API routes behave differently than Pages Router
What to Check:
- Vercel build logs for Next.js version detection
- Any warnings about incompatible features
- Check Vercel's Next.js 16 support status
Potential Fix: Downgrade to Next.js 15.x temporarily to test:
{
"dependencies": {
"next": "^15.0.0"
}
}
Theory 4: Environment Variable Issue
Likelihood: LOW
Evidence:
- DATABASE_URL is configured
- Pages work (they might not need env vars)
- API routes use Prisma (requires DATABASE_URL)
What to Check:
-
Vercel Dashboard → Settings → Environment Variables
- Verify DATABASE_URL is set for Production
- Verify it's not blocked or empty
- Check if other vars are needed
-
Build logs for Prisma generation errors
Potential Fix: None - user confirmed DATABASE_URL is set.
Theory 5: Build Output Issue
Likelihood: MEDIUM-HIGH
Evidence:
vercel inspectdoesn't show API routes- Only pages are listed as functions
- Build completes successfully (38-41 seconds)
What to Check:
-
Build logs: Does Next.js report building API routes?
- Look for "Route (app)" or "λ" indicators for API routes
- Compare to local build output
-
Check if Vercel is using correct build output structure
- App Router uses
.next/server/app/ - Pages Router uses
.next/server/pages/
- App Router uses
Potential Fix: Try forcing Vercel to recognize the build:
{
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
]
}
(Note: builds is legacy, modern Next.js should auto-detect)
Recommended Next Steps
Immediate Actions (High Priority)
-
Check Vercel Project Settings
- Go to Vercel Dashboard → tpmjs-web project
- Verify Framework Preset is "Next.js"
- Verify Root Directory is
apps/web - Screenshot settings for reference
-
Review Build Logs
- Go to latest deployment
- Download complete build logs
- Search for:
- "Route (app)" - should show API routes
- "λ" - should show API functions
- "api" - any mentions
- Errors or warnings
-
Check Domain Configuration
- Verify only ONE project has tpmjs.com domain
- Remove domain from
v0-tool-registry-pageproject if present - Check DNS settings aren't redirecting
-
Test Simple API Route
- Create minimal API route:
// apps/web/src/app/api/test/route.ts export async function GET() { return new Response('Hello from API', { status: 200 }); }- Deploy and test
- If this doesn't work, confirms platform issue
Investigation Actions (Medium Priority)
-
Compare Working vs Non-Working
- Find a deployment where pages DO work
- Compare build output between page routes and API routes
- Look for differences in how they're compiled
-
Test Vercel CLI Deploy
- Deploy directly via CLI:
vercel --prod - Check if behavior differs from Git-based deploy
- Might reveal configuration issues
- Deploy directly via CLI:
-
Check Vercel Function Logs
- Even though functions aren't in build output, try:
vercel logs <deployment-url> --since 1h- Look for any API route invocations or errors
-
Review Turbo Configuration
# Check turbo.json for Next.js build config cat /turbo.json # Verify build runs correctly locally pnpm --filter=@tpmjs/web build
Alternative Approaches (If Above Fails)
-
Create New Vercel Project
- Import from Git fresh
- Use identical settings
- Test if fresh project works
-
Contact Vercel Support
- This may be a platform bug with Next.js 16
- Provide this document as context
- Ask specifically why API routes aren't in build output
-
Temporary Workaround
- Deploy API routes separately (different service)
- Use Vercel proxy to route
/api/*to separate deployment - Not ideal but unblocks development
Environment Variables Needed
# Required for API routes
DATABASE_URL="postgresql://..."
# Optional (check if needed)
NODE_ENV="production"
NEXT_PUBLIC_* # Any public env vars
Build Commands Reference
Local Development
# Install dependencies
pnpm install
# Generate Prisma client
pnpm --filter=@tpmjs/db db:generate
# Run development server
pnpm --filter=@tpmjs/web dev
# Build for production
pnpm --filter=@tpmjs/web build
# Test build locally
pnpm --filter=@tpmjs/web start
Vercel Configuration
Current:
{
"buildCommand": "cd ../.. && pnpm --filter=@tpmjs/web build",
"installCommand": "pnpm install"
}
Alternative to try:
{
"buildCommand": "cd ../.. && turbo build --filter=@tpmjs/web",
"installCommand": "pnpm install",
"framework": "nextjs"
}
Key Files to Review
/apps/web/next.config.ts- Next.js configuration/apps/web/vercel.json- Vercel app-level config/vercel.json- Vercel root config/turbo.json- Turborepo configuration/apps/web/.next/routes-manifest.json- Route definitions/apps/web/.next/build-manifest.json- Build output- Vercel build logs (from dashboard)
Questions for Vercel Support
If escalating to Vercel support, ask:
-
Why are API routes not appearing in the build output (
vercel inspect) when pages are deploying correctly? -
Is there a known issue with Next.js 16 App Router API routes in Turborepo monorepos?
-
What's the correct way to configure
vercel.jsonfor a Next.js 16 app in a monorepo with custom build commands? -
Could having two projects (
tpmjsandv0-tool-registry-page) with the same domain cause routing issues? -
Are there any specific requirements for deploying Next.js 16 API routes that differ from Next.js 15?
Related Documentation
- Next.js 16 Upgrade Guide
- Vercel Next.js Deployment
- Vercel Functions Configuration
- Turborepo with Vercel
- Next.js App Router API Routes
Recent Commits Related to This Issue
065196d fix(build): simplify Vercel build command
8281f8f fix(build): disable Turbopack for Vercel deployment
748ca4d fix(api): configure Vercel functions for API routes with maxDuration
b93cd42 fix: remove redirects to resolve redirect loop
c42bfb6 fix(redirects): exclude API routes from www redirect
9be3af7 fix(routing): move www redirect from vercel.json to Next.js config
a92dfff fix(build): add workspace packages to Next.js transpilePackages
cc6c824 fix(vercel): configure Turborepo monorepo build for apps/web
✅ CONCLUSION - ROOT CAUSE IDENTIFIED
The Real Problem
Vercel is NOT detecting this project as a Next.js application.
When Vercel doesn't detect Next.js, it:
- Uses
@vercel/static-builderinstead of@vercel/next - Treats the deployment as a static site
- Deploys pages (static HTML) successfully
- Completely drops all App Router API routes
- Never generates serverless functions for
/api/*routes
This explains EVERY symptom:
- ✅ Pages work (they're static files)
- ❌ API routes timeout (they were never deployed)
- ❌ No
λ api/*in build output (functions don't exist) - ❌ Direct Vercel URLs timeout (not a DNS issue)
- ❌ "Redirecting..." on custom domain (wrong project owns the domain)
Why Vercel Doesn't Detect Next.js
1. Wrong Root Directory
- Vercel project likely has Root Directory set to
.or empty - Should be exactly:
apps/web - A single character difference breaks Next.js detection
2. Wrong Framework Preset
- When Vercel can't auto-detect Next.js (due to monorepo + wrong root)
- It defaults to Framework Preset = "Other"
- "Other" uses static builder, not Next.js builder
3. Domain Conflict
- Two projects exist:
tpmjsandv0-tool-registry-page - Both have
tpmjs.comdomain assigned - Requests randomly route to wrong project
- "Redirecting..." comes from the old project, not your code
4. Custom Build Commands
- Custom build commands can bypass Vercel's auto-detection
- Should let Vercel auto-detect and use default commands
Evidence
Local build works:
$ ls apps/web/.next/server/app/api/
health/ stats/ sync/ tools/
# All API routes compile correctly
Vercel deployment missing API routes:
$ vercel inspect https://tpmjs-1chh44d1u-tpmjs.vercel.app
Builds
├── λ tool/[slug] # Pages present
├── λ _global-error # Pages present
└── 56 output items hidden
# NO api/health, NO api/tools, NO api/* at all
This is not a Next.js bug, not a Turborepo bug, not a code issue. This is pure Vercel project misconfiguration.
The Fix (4 Steps, 5 Minutes)
See VERCEL_FIX_CHECKLIST.md for detailed instructions.
Quick version:
- Set Root Directory to
apps/web(exactly) - Set Framework Preset to "Next.js"
- Remove
tpmjs.comdomain fromv0-tool-registry-pageproject - Clear custom build commands (let Vercel auto-detect)
No code changes needed. Just fix Vercel dashboard settings.
After the Fix
Next deployment will show:
$ vercel inspect <new-deployment>
Builds
├── λ api/health
├── λ api/tools
├── λ api/sync/changes
├── λ api/tools/[id]
├── λ api/tools/[slug]
├── λ tool/[slug]
...
And API routes will work instantly:
$ curl https://tpmjs.com/api/health
{"status":"ok","timestamp":"..."}
Summary for ChatGPT
The core issue is that Vercel is not detecting the project as Next.js due to incorrect Root Directory, Framework Preset set to "Other", domain conflicts between two projects, and custom build commands bypassing auto-detection.
When Vercel doesn't detect Next.js, it uses a static site builder which deploys pages but completely drops all App Router API routes from the build. This is why pages work but all /api/* requests timeout - the API route serverless functions were never created.
The fix is purely configuration - no code changes needed. Set Root Directory to apps/web, Framework Preset to "Next.js", remove the domain from the old project, and clear custom build commands. See VERCEL_FIX_CHECKLIST.md for step-by-step instructions.