What Causes This Error?
This error occurs when you try to replace the entire locals object instead of modifying its properties. Astro prevents reassigning locals to ensure consistent behavior across middleware and pages.
The Problem
// src/middleware.ts
export const onRequest = defineMiddleware((context, next) => {
// ❌ Reassigning the entire object
context.locals = {
user: 'John',
role: 'admin',
};
return next();
});
The Fix
Modify Properties Instead
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware((context, next) => {
// ✅ Add/modify properties
context.locals.user = 'John';
context.locals.role = 'admin';
return next();
});
Common Scenarios
Bulk Property Assignment
// ❌ Can't replace entire object
const userData = { user: 'John', role: 'admin', permissions: [] };
context.locals = userData;
// ✅ Use Object.assign
Object.assign(context.locals, userData);
// ✅ Or spread with Object.assign
Object.assign(context.locals, {
user: 'John',
role: 'admin',
permissions: [],
});
From External Source
export const onRequest = defineMiddleware(async (context, next) => {
const sessionData = await getSession(context.cookies);
// ❌ Can't replace locals
// context.locals = sessionData;
// ✅ Assign properties
if (sessionData) {
context.locals.user = sessionData.user;
context.locals.token = sessionData.token;
context.locals.expiresAt = sessionData.expiresAt;
}
return next();
});
Conditional Object
export const onRequest = defineMiddleware((context, next) => {
const isAdmin = checkAdmin(context);
// ❌ Can't conditionally reassign
// context.locals = isAdmin ? adminLocals : userLocals;
// ✅ Conditionally set properties
if (isAdmin) {
context.locals.role = 'admin';
context.locals.permissions = ['read', 'write', 'delete'];
} else {
context.locals.role = 'user';
context.locals.permissions = ['read'];
}
return next();
});
Helper Function
// Helper to safely set locals
function setLocals(context, data: Record<string, unknown>) {
for (const [key, value] of Object.entries(data)) {
context.locals[key] = value;
}
}
export const onRequest = defineMiddleware((context, next) => {
// ✅ Use helper
setLocals(context, {
user: 'John',
role: 'admin',
});
return next();
});
TypeScript Pattern
// src/env.d.ts
declare namespace App {
interface Locals {
user?: { id: string; name: string };
isAuthenticated: boolean;
requestId: string;
}
}
// src/middleware.ts
export const onRequest = defineMiddleware((context, next) => {
// TypeScript knows valid properties
context.locals.isAuthenticated = false;
context.locals.requestId = crypto.randomUUID();
return next();
});
Clearing Properties
// ❌ Can't clear by reassigning
// context.locals = {};
// ✅ Delete specific properties
delete context.locals.user;
delete context.locals.token;
// ✅ Or set to undefined/null
context.locals.user = undefined;
Multiple Middleware
// First middleware sets some locals
const authMiddleware = defineMiddleware((context, next) => {
context.locals.user = getUser();
return next();
});
// Second middleware adds more
const analyticsMiddleware = defineMiddleware((context, next) => {
// ✅ Both can add to same locals
context.locals.requestTime = Date.now();
return next();
});
export const onRequest = sequence(authMiddleware, analyticsMiddleware);
Why This Restriction Exists
Locals is a shared object across:
- All middleware in sequence
- The page/endpoint handler
- Any components that access Astro.locals
Reassigning would break this sharing.
Instead, properties are accumulated.
Quick Checklist
- Never use
context.locals = {...} - Use
context.locals.prop = value - Use
Object.assign(context.locals, data) - Delete individual properties to clear
- Type locals in
env.d.ts