Next.js
Integrate redirect lookups with Next.js middleware using the Edge Query API
Overview
This guide shows you how to integrate redirect lookups into your Next.js application using the Edge Query API. You'll learn two patterns: real-time middleware fetches for dynamic redirects and build-time sync for static redirect configuration.
Estimated time: ~15 minutes
Prerequisites:
- Next.js 15+ project
- API key from the dashboard
- Project ID from your redirect project
Request Flow
Here's how redirect lookups work in Next.js middleware:
flowchart LR
A[Client Request] --> B[Next.js Edge Runtime]
B --> C[middleware.ts]
C --> D{API Lookup}
D -->|200 Match| E[NextResponse.redirect]
D -->|204 No Match| F[NextResponse.next]
D -->|Error| F
E --> G[Redirect Response]
F --> H[Continue to Route]Pattern 1: Real-Time Middleware Fetch
The primary pattern for Next.js uses Edge Runtime middleware to fetch redirect rules in real-time. This approach keeps redirects dynamic and always up-to-date.
Step 1: Create Middleware File
Create middleware.ts at your project root (not inside app/ or pages/):
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
interface RedirectResponse {
path: string;
destination: string;
statusCode: number;
projectId: string;
}
export async function middleware(request: NextRequest) {
const path = request.nextUrl.pathname;
try {
const response = await fetch(
`https://api.3xx.app/v1/lookup?path=${encodeURIComponent(path)}&projectId=${process.env.REDIRECTIONS_PROJECT_ID}`,
{
headers: {
'X-API-Key': process.env.REDIRECTIONS_API_KEY || '',
},
}
);
if (response.status === 200) {
const data: RedirectResponse = await response.json();
return NextResponse.redirect(
new URL(data.destination, request.url),
data.statusCode
);
}
// 204 = no match, pass through
return NextResponse.next();
} catch (error) {
console.error('Redirect lookup failed:', error);
// On error, pass through to avoid breaking the site
return NextResponse.next();
}
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};Critical: The matcher config excludes static assets and Next.js internals. Without this, the middleware will make API calls on every CSS file, image, and internal request, wasting API quota.
Step 2: Configure Environment Variables
Create .env.local in your project root:
REDIRECTIONS_API_KEY=YOUR_API_KEY
REDIRECTIONS_PROJECT_ID=YOUR_PROJECT_IDEnvironment variables in Next.js middleware work differently than in API routes. The Edge Runtime has access to process.env without needing NEXT_PUBLIC_ prefix, but values must be available at build time.
Step 3: Test the Integration
Start your development server:
npm run devVisit a path that should redirect, for example http://localhost:3000/test-path. Open your browser DevTools Network tab to verify:
- The request shows a 301 or 302 status
- The Location header points to the correct destination
- No errors in the console
Pattern 2: Build-Time Sync
For high-traffic applications or when you want to avoid API calls on every request, sync redirects at build time into Next.js's native redirect configuration.
Step 1: Create Sync Script
Create scripts/sync-redirects.js:
const fs = require('fs');
const path = require('path');
async function syncRedirects() {
const apiKey = process.env.REDIRECTIONS_API_KEY;
const projectId = process.env.REDIRECTIONS_PROJECT_ID;
if (!apiKey || !projectId) {
throw new Error('Missing REDIRECTIONS_API_KEY or REDIRECTIONS_PROJECT_ID');
}
console.log('Fetching redirects from API...');
const response = await fetch(
`https://api.3xx.app/v1/export?format=csv&projectId=${projectId}`,
{
headers: {
'X-API-Key': apiKey,
},
}
);
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
const csv = await response.text();
const lines = csv.split('\n').slice(1); // Skip header
const redirects = lines
.filter(line => line.trim())
.map(line => {
const [path, destination, statusCode] = line.split(',');
return {
source: path,
destination: destination,
permanent: parseInt(statusCode) === 301,
};
});
const outputPath = path.join(process.cwd(), 'redirects.json');
fs.writeFileSync(outputPath, JSON.stringify(redirects, null, 2));
console.log(`✓ Synced ${redirects.length} redirects to redirects.json`);
}
syncRedirects().catch(err => {
console.error('Sync failed:', err);
process.exit(1);
});Step 2: Update Next.js Config
Modify next.config.js to read the synced redirects:
const fs = require('fs');
const path = require('path');
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
const redirectsPath = path.join(process.cwd(), 'redirects.json');
if (!fs.existsSync(redirectsPath)) {
console.warn('redirects.json not found, skipping redirects');
return [];
}
const redirects = JSON.parse(fs.readFileSync(redirectsPath, 'utf-8'));
return redirects;
},
};
module.exports = nextConfig;Step 3: Add to Build Pipeline
Update package.json to run the sync before builds:
{
"scripts": {
"dev": "next dev",
"prebuild": "node scripts/sync-redirects.js",
"build": "next build",
"start": "next start"
}
}Step 4: Test the Sync
Run the sync script manually:
node scripts/sync-redirects.jsCheck that redirects.json was created with your redirect rules. Then build:
npm run buildThe build output should show your redirects being applied.
Build-time redirects only update when you rebuild and redeploy your application. For dynamic redirects that change frequently, use Pattern 1 (middleware fetch).
App Router vs Pages Router
Both patterns work identically for App Router and Pages Router:
- Middleware (
middleware.ts) runs at the edge before any routing, so it works the same for both routers - Build-time redirects in
next.config.jsalso work for both routers
The only router-specific consideration: App Router's Route Handlers (app/api/*/route.ts) and Pages Router's API Routes (pages/api/*.ts) don't affect middleware behavior. Middleware runs before these handlers.
Fallback Behavior
Always implement graceful fallback to avoid breaking your site if the API is unreachable:
export async function middleware(request: NextRequest) {
try {
const response = await fetch(/* ... */);
if (response.status === 200) {
const data: RedirectResponse = await response.json();
return NextResponse.redirect(
new URL(data.destination, request.url),
data.statusCode
);
}
return NextResponse.next();
} catch (error) {
// Log for debugging, but don't break the request
console.error('Redirect lookup failed:', error);
return NextResponse.next();
}
}Advanced: Edge Caching
For even better performance, cache API responses at the edge:
const response = await fetch(
`https://api.3xx.app/v1/lookup?path=${encodeURIComponent(path)}&projectId=${projectId}`,
{
headers: {
'X-API-Key': apiKey,
},
next: { revalidate: 300 }, // Cache for 5 minutes
}
);Testing
- Start dev server:
npm run dev - Test a redirect: Visit a path that should redirect in your browser
- Check DevTools: Verify the redirect status code and destination in the Network tab
- Check console: Look for any errors in the browser console or server logs
Testing Checklist
- Known redirect path returns correct status code (301/302)
- Known redirect path goes to correct destination
- Non-existent path passes through (no redirect)
- Static assets don't trigger API calls (check Network tab)
- No errors in console
Troubleshooting
Middleware Not Running
Symptom: Redirects don't work at all.
Cause: middleware.ts must be at project root, not inside app/ or pages/.
Fix: Move middleware.ts to the same level as next.config.js.
Matcher Too Broad
Symptom: API quota exhausted quickly, many 204 responses.
Cause: Matcher includes static files, causing API calls for every image, CSS file, etc.
Fix: Use the recommended matcher:
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};Environment Variables Not Available
Symptom: undefined API key or project ID in middleware.
Cause: Variables not configured correctly for Edge Runtime.
Fix:
- Ensure
.env.localexists and has correct values - Restart dev server after changing
.env.local - For production, set environment variables in your deployment platform (Vercel, etc.)
Edge Runtime Limitations
Symptom: Node.js APIs cause errors in middleware.
Cause: Next.js middleware runs in Edge Runtime, which doesn't support all Node.js APIs.
Fix: Use only Web APIs (fetch, URL, Response, etc.) in middleware. For Node.js APIs, use API Routes or Server Components.
For more troubleshooting help, see the Troubleshooting Guide.