Demo Blog 4: Scaling Edge Middleware & Background Jobs
A deep dive into building low-latency edge middlewares, handling async workloads, and keeping background workers rock solid in Next.js.
Welcome to Demo Blog 4. Once you have authentication, database migrations, and payments working smoothly, the next hurdle every growing SaaS faces is latency and background work: keeping edge requests under 50ms while running heavy background tasks without timeouts.
Here is how we design low-overhead edge middleware and reliable background jobs in production.
Edge Middleware: Fast, Minimal, and Resilient
Next.js middleware runs on edge runtime environments (V8 isolates). That means no Node.js built-ins like node:crypto or heavyweight ORMs like Prisma.
The edge layer should do exactly three things:
- Route protection: Verify session validity with lightweight cryptographic checks or edge-compatible tokens.
- Geo-routing & redirects: Redirect users to locale/region endpoints before page rendering.
- Security headers: Inject CSP, HSTS, and X-Frame-Options headers on every outbound response.
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/request';
export function middleware(req: NextRequest) {
const sessionToken = req.cookies.get('__Secure-authjs.session-token');
const isProtectedPath = req.nextUrl.pathname.startsWith('/dashboard');
if (isProtectedPath && !sessionToken) {
const loginUrl = new URL('/login', req.url);
loginUrl.searchParams.set('callbackUrl', req.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
const response = NextResponse.next();
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*'],
};
Keep edge middleware under 1MB bundle size and under 10ms execution time. Anything heavier belongs in Route Handlers or Server Actions.
Background Jobs: Surviving Serverless Timeouts
Serverless functions have execution limits. When a user exports 50,000 records or triggers AI generation, processing it in the HTTP request will fail with a 504 Gateway Timeout.
Instead, use an asynchronous queue pattern:
| Pattern | Best For | Typical Latency |
|---|---|---|
| Inngest / Trigger.dev | Complex multi-step workflows, retries | Sub-second dispatch |
| BullMQ + Redis | High-volume message processing | Real-time queueing |
| Postgres SKIP LOCKED | Lightweight queues without extra infra | 1-5 second polling |
Example: Async Job Dispatch with Webhooks
// app/api/export/route.ts
import { NextResponse } from 'next/server';
import { inngest } from '@/lib/inngest';
import { auth } from '@/lib/auth';
export async function POST(req: Request) {
const session = await auth();
if (!session?.user?.id) {
return new NextResponse('Unauthorized', { status: 401 });
}
// Enqueue job immediately — return 202 Accepted
const event = await inngest.send({
name: 'user/export.requested',
data: { userId: session.user.id },
});
return NextResponse.json({ jobId: event.ids[0] }, { status: 202 });
}
Key Takeaways
- Keep middleware lean: Don’t import heavy database clients at the edge.
- Offload long tasks: Return
202 Acceptedimmediately and process work asynchronously. - Notify via WebSockets or Webhooks: Let the client poll or listen for job completion.
That’s the architecture that keeps your SaaS feeling instantaneous even under heavy traffic.