Middleware and Cookies

By Syncfusion®, Inc. · Last updated

What middleware is, src/middleware.ts, defineMiddleware(), next() and changing responses, sequence(), redirects and rewrites in middleware, sharing data with locals, App.Locals, and reading, setting and deleting cookies with their options.

Middleware

  • Middleware = code that runs before a page or endpoint, for every request.
  • It can read the request, add data for the page, or answer early with a redirect.
  • It can also run after the page, to change the response.
  • In this lesson, you will write middleware and use cookies, small pieces of data stored in the visitor's browser.

Simple lesson: middleware is a gate every request passes through.

A Real-World Example

  • Asha wants every page to know the visitor's preferred language and to log how long each page takes.
  • Adding that code to every page would mean copying it many times.
  • She writes it once in src/middleware.ts. Every request runs it.

Press ▶ Run. Visit both pages. The Terminal shows the middleware running before each one.

What happened:

  • onRequest ran first. It put greeting into context.locals.
  • await next() ran the page. The page read Astro.locals.greeting.
  • After next(), the middleware ran again and logged the time.

What You Will Learn

In this lesson, you will learn:

  1. How to create middleware in src/middleware.ts.
  2. What next() does, and how to change the response.
  3. How to chain several middleware with sequence().
  4. How to redirect or rewrite from middleware.
  5. How to share data with pages through locals.
  6. How to read, set and delete cookies.
  7. Which cookie options keep them safe.

Creating Middleware

src/middleware.ts or src/middleware.js

  • [TS] Create src/middleware.ts. [JS] Create src/middleware.js.
  • Export a function called onRequest.
  • It runs for every on-demand request. For static pages, it runs once each, at build time.

defineMiddleware()

  • defineMiddleware() from astro:middleware wraps your function.
  • It changes nothing at run time. It gives your editor the right types for context and next.

next() and Modifying Responses

  • next() = "run the rest: other middleware, then the page".
  • It returns the page's Response.
  • After it, you can change the response, such as adding a header: response.headers.set("X-Powered-By", "Asha").
  • If you never call next(), the page never runs. You must then return your own response.

Animation: a request enters middleware, which calls next(), the page runs, and the response passes back through the middleware before reaching the browser.

Browser

Middleware before: set locals await next() after: add headers

Page

The request goes in through the middleware, and the response comes back out through it.

Chaining with sequence()

  • sequence(a, b, c) from astro:middleware runs several middleware in order.
  • Each one calls next() to pass control to the next.
  • Keep each middleware small, with one job: logging, auth, language and so on.

Order matters in sequence(). Put logging first so it sees every request, and auth before anything that needs to know the user.

Press ▶ Run. Read the order in the Terminal: in through log, then lang, out in reverse.

Simple lesson: onRequest runs around every request. next() runs the page. sequence() chains several.

Redirects, Rewrites and locals

Redirecting and Rewriting from Middleware

  • return context.redirect("/login") = send the visitor elsewhere. The page does not run.
  • return context.rewrite("/maintenance") = show another page's content at the same address.
  • Use these for rules that apply to many pages, such as "every /admin page needs a login".

context.locals

  • locals = an object that lives for one request only.
  • Middleware writes to it: context.locals.user = ....
  • Pages read it: Astro.locals.user. Endpoints read context.locals.user.
  • Nothing in locals reaches the browser unless a page shows it.

[TS] Typing App.Locals

  • Describe your locals in src/env.d.ts, so pages know their types:
declare namespace App {
  interface Locals {
    lang: "en" | "ta";
    user?: { name: string };
  }
}
  • [JS] You do not need this. locals is a plain object.

Config redirects (from Routing) are simpler for fixed lists. Middleware redirects are for rules, such as "anything that starts with /old-blog".

Simple lesson: middleware can redirect or rewrite, and passes data to pages through locals.

Cookies

What Cookies Are

  • Cookie = a small piece of text the browser stores for one website, and sends back with every request to it.
  • Examples: a theme choice, a "seen the banner" flag, a session ID.
  • A cookie is per visitor. Each browser has its own.

Reading Cookies

  • Astro.cookies.get("theme") returns the cookie, or undefined.
  • Astro.cookies.has("theme") returns true or false.
  • Read its value in the form you need:

    • .value = the text,
    • .number() = a number,
    • .boolean() = true or false,
    • .json() = an object, if you stored JSON.

Setting Cookies

  • Astro.cookies.set("theme", "dark", { path: "/" }).
  • Objects are stored as JSON: Astro.cookies.set("prefs", { fontSize: 18 }).
  • Setting a cookie only works in on-demand pages, endpoints, middleware and actions.

Deleting Cookies

  • Astro.cookies.delete("theme", { path: "/" }).
  • Use the same path as when you set it.

Cookies in Endpoints and Middleware

  • In endpoints, actions and middleware, use context.cookies. It works the same way.

Press ▶ Run. Visit the page a few times. Then reset the counter. The Terminal shows each cookie change.

Cookie Options

  • path = which addresses get the cookie. "/" means the whole site.
  • maxAge = seconds until it expires. expires = an exact date. Without either, it ends when the browser closes.
  • httpOnly = browser JavaScript cannot read it. Use it for anything sensitive.
  • secure = only sent over HTTPS.
  • sameSite = whether other websites' requests carry it:

    • "strict" = never,
    • "lax" = only for normal link clicks (a good default),
    • "none" = always (needs secure).
  • domain = share it with sub-domains, such as blog.asha.dev.
  • partitioned = keep a third-party cookie separate per site. Rarely needed.

Cookies travel with every request and the visitor can edit them. Never store passwords or trust a cookie's value without checking it. Keep private data on the server, in a session.

Merging and Reading Set-Cookie Headers (Advanced)

  • cookies.merge(other) joins cookies from another AstroCookies object.
  • cookies.headers() lists the Set-Cookie header values Astro will send. Useful in tests and custom middleware.

Press ▶ Run. Pick a theme. It is saved as a JSON cookie, and read back on every page.

Simple lesson: get(), has(), set() and delete() cookies. Use httpOnly, secure and sameSite to keep them safe.

TL;DR

  1. Middleware = onRequest(context, next) in src/middleware.ts. Runs around every request.
  2. next() = run the page. Change the response after it.
  3. sequence(a, b) = run several middleware in order.
  4. context.redirect() / context.rewrite() = answer early.
  5. locals = per-request data. Middleware writes, pages read Astro.locals.
  6. Cookie = small data in the browser, sent with every request.
  7. Read = cookies.get(name)?.value / .number() / .boolean() / .json(), cookies.has(name).
  8. Write = cookies.set(name, value, options), cookies.delete(name, { path }).
  9. Options = path, maxAge, httpOnly, secure, sameSite, domain.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Not returning next()Calling it is enoughreturn next() or return its responseThe page gets no response
Middleware in the wrong placeAny file name workssrc/middleware.ts exporting onRequestIt never runs
Setting a cookie on a static pageStatic pages have requestsSet cookies on on-demand routesError: cookies cannot be set
Deleting with a different pathdelete() finds every copyUse the same path as set()The cookie stays
Secrets in a cookieCookies are privateStore an ID, keep data on the serverVisitors can read or change it
No httpOnly on a session cookieScripts need itUse httpOnly: trueInjected scripts can steal it

Check Yourself

Exercises

What to Remember

  • Middleware in src/middleware.ts runs around every request. next() runs the page.
  • sequence() chains small middleware. locals passes data to pages.
  • Middleware can redirect or rewrite before the page runs.
  • Cookies live in the browser. Read with get(), write with set(), remove with delete().
  • Use httpOnly, secure and sameSite, and never trust a cookie blindly.

Next: Sessions, where data stays on the server and only an ID lives in a cookie.