Netlify Integration
When to Use This Skill
Use this skill when you need to:
Deployment & Configuration:
- Deploy a Next.js application to Netlify
- Configure
netlify.tomlfor build settings, functions, redirects, or headers - Set up environment variables in Netlify Dashboard or CLI
- Configure continuous deployment from Git (GitHub, GitLab, Bitbucket)
Functions Development:
- Create Netlify serverless functions
- Handle webhooks (Twilio, Telnyx, Stripe, etc.)
- Debug function timeout issues (10s limit on free tier, 26s on Pro)
- Implement background processing for long-running tasks
Troubleshooting:
- Fix webhook timeout errors (especially for SMS/telephony apps)
- Debug "Function not found" (404) errors
- Resolve environment variable issues
- Fix build failures or deployment errors
SMS/Telephony Projects:
- Working on projects like Twilio-Aldea that handle SMS webhooks
- Implementing immediate webhook acknowledgment + background processing
- Validating webhook signatures (Twilio, Telnyx)
Quick Reference
1. Basic Netlify Function Handler
import type { Handler, HandlerEvent, HandlerContext } from "@netlify/functions";
export const handler: Handler = async (
event: HandlerEvent,
context: HandlerContext
) => {
try {
const body = JSON.parse(event.body || "{}");
// Process request
console.log("Request received:", body);
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ success: true }),
};
} catch (error) {
console.error("Error:", error);
return {
statusCode: 500,
body: JSON.stringify({ error: "Internal server error" }),
};
}
};
2. Webhook with Immediate Response (Critical Pattern)
Solves the 60% message loss problem - return 200 immediately, process async:
export const handler: Handler = async (event, context) => {
// Validate webhook signature
const isValid = validateWebhook(event);
if (!isValid) {
return { statusCode: 401, body: "Unauthorized" };
}
// IMMEDIATE: Return 200 OK (< 200ms)
const response = {
statusCode: 200,
body: JSON.stringify({ received: true }),
};
// BACKGROUND: Process async (don't await)
processWebhookAsync(event.body).catch(console.error);
return response;
};
3. Essential netlify.toml for Next.js
[build]
command = "npm run build"
publish = ".next"
[build.environment]
NODE_VERSION = "18"
NEXT_TELEMETRY_DISABLED = "1"
# OpenNext adapter auto-detects Next.js — no plugin entry needed.
# Legacy: @netlify/plugin-nextjs is replaced by @opennextjs/netlify.
[functions]
directory = ".netlify/functions"
node_bundler = "esbuild"
# API rewrites
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
force = true
# Security headers
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
4. Environment Variables Setup (CLI)
Prerequisites - Link Project First:
# Option 1: Interactive linking
netlify link
# Option 2: Manual linking (create .netlify/state.json)
mkdir -p .netlify
echo '{"siteId":"YOUR-SITE-ID-HERE"}' > .netlify/state.json
# Find your site ID
netlify sites:list # Shows all sites with their IDs
# Verify link worked
netlify status
Managing Environment Variables:
# Set single variable (use --force to skip confirmation)
netlify env:set VARIABLE_NAME value
netlify env:set VARIABLE_NAME "value with spaces" --force
# Set for specific context (production, deploy-preview, branch-deploy, dev)
netlify env:set VARIABLE_NAME value --context production
# Import from .env file
netlify env:import .env.production
# List all variables
netlify env:list
# Get specific variable value
netlify env:get VARIABLE_NAME
# Delete variable
netlify env:unset VARIABLE_NAME
# Clone variables from another site
netlify env:clone --from OTHER_SITE_ID
Bulk Add Example (from shell):
# Add multiple env vars in sequence
netlify env:set API_KEY "sk-xxx" --force
netlify env:set ENABLE_FEATURE "true" --force
netlify env:set NEXT_PUBLIC_APP_URL "https://myapp.netlify.app" --force
Note: After adding/changing env vars, you may need to trigger a redeploy for changes to take effect in production.
5. Function Timeout Configuration
[functions]
directory = ".netlify/functions"
timeout = 10 # Default for all functions
# Increase timeout for specific function
[[functions."webhook"]]
timeout = 26 # Max for Pro tier (10s for free)
6. Raw Body Parsing for Signature Validation
Critical for Twilio/Telnyx webhook signature validation:
export const config = {
api: {
bodyParser: false, // Disable Next.js body parsing
},
};
async function readRawBody(req: NextApiRequest): Promise<string> {
return new Promise<string>((resolve, reject) => {
let data = '';
req.setEncoding('utf8');
req.on('data', (chunk) => { data += chunk; });
req.on('end', () => resolve(data));
req.on('error', reject);
});
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const rawBody = await readRawBody(req);
// Validate signature with raw body
const isValid = validateSignature(rawBody, req.headers['x-twilio-signature']);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
}
7. Database Error Logging Pattern
export const handler: Handler = async (event) => {
try {
await processMessage(data);
return { statusCode: 200, body: 'Success' };
} catch (error: unknown) {
const err = error instanceof Error ? error : new Error(String(error));
console.error('[Function] Error:', err);
// Log to database for visibility
await supabase.from('processing_errors').insert({
error_message: err.message,
error_stack: err.stack,
created_at: new Date().toISOString(),
});
return { statusCode: 500, body: `Error: ${err.message}` };
}
};
8. Connection Pooling Pattern
// Reuse connections across function invocations
let supabaseClient: any = null;
function getSupabaseClient() {
if (!supabaseClient) {
supabaseClient = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
);
}
return supabaseClient;
}
export const handler: Handler = async (event, context) => {
const supabase = getSupabaseClient();
// Use cached client - reduces cold start time
};
9. Dynamic Base URL Detection
function getBaseUrl(req: NextApiRequest): string {
const proto = (req.headers['x-forwarded-proto'] as string) || 'https';
const host = (req.headers['x-forwarded-host'] as string) || (req.headers.host as string);
return `${proto}://${host}`;
}
// Works in production, preview deploys, and localhost
const baseUrl = getBaseUrl(req);
const backgroundUrl = `${baseUrl}/.netlify/functions/background`;
10. Input Validation with Zod
import { z } from "zod";
const WebhookSchema = z.object({
from: z.string().min(10).max(15),
to: z.string().min(10).max(15),
body: z.string().min(1).max(1600),
messageId: z.string().uuid(),
});
export const handler: Handler = async (event, context) => {
try {
const data = JSON.parse(event.body || "{}");
const validated = WebhookSchema.parse(data);
await processMessage(validated);
return { statusCode: 200, body: JSON.stringify({ success: true }) };
} catch (error) {
if (error instanceof z.ZodError) {
return {
statusCode: 400,
body: JSON.stringify({ error: "Validation failed", details: error.errors }),
};
}
throw error;
}
};
Key Concepts
Netlify Functions
- Serverless functions that run on AWS Lambda
- Loc