middleware.ts
Redirects, rewrites, response headers and auth guards declared in TypeScript at the project root, and enforced by the Rust server before routing.
import { defineMiddleware } from '@gio.js/core';
export default defineMiddleware({
redirects: [{ from: '/old-blog/*rest', to: '/blog/*rest', status: 308 }],
rewrites: [{ from: '/docs/*rest', to: '/guide/*rest' }],
headers: [{ path: '/api/*rest', headers: { 'x-api-version': '1' } }],
guards: [{ path: '/account/*rest', requireSession: true, redirectTo: '/login' }],
});Reference
File name and location
middleware.ts or middleware.js (.ts first) in the project root, next to app/ - not inside it, and not in a src/ folder. It is optional.
Default export
An object with up to four lists. defineMiddleware() returns it unchanged and types it as MiddlewareRules.
| Key | Type | Default | Description |
|---|---|---|---|
redirects | { from: string; to: string; status?: 301 | 302 | 307 | 308 }[] | - | Answer with a redirect. status defaults to 302. |
rewrites | { from: string; to: string }[] | - | Serve another path while the browser URL stays the same. |
headers | { path: string; headers: Record<string, string> }[] | - | Add or override response headers on matching paths. |
guards | MiddlewareGuard[] | - | Redirect requests without a cookie (requireCookie) or without a valid session (requireSession: true, checking the gio_session cookie or the one requireCookie names) to redirectTo, a path on this site starting with one /, with a 302. |
Patterns are the routing ones: literal segments, :param for one segment and *rest for the rest of the path (which may be empty), substituted into to by name. The pattern language and each rule kind are explained in the Middleware guide.
How it runs
- Once, at worker startup. The worker imports the file and sends the rules to the Rust server, which compiles them next to the rules from
gio.toml. No JavaScript runs per request: a request is redirected, rewritten or refused before it reaches Node, and no header the client sends can skip a rule. - Order. Guards, then redirects, then rewrites, with
gio.tomlrules tried beforemiddleware.tsrules within each phase. Every matching guard must admit the request (the first that refuses redirects); among redirects and among rewrites the first match wins. Header rules apply to the response, redirects and guard answers included. - Reloads. The rules are read again whenever the worker restarts. In development, saving the file restarts it.
/_gio/*(the server's own endpoints) is never matched.
Validation
The file is strict, like gio.toml: a rule is never dropped while the app serves, because a dropped guard would leave its path open. The worker refuses to boot, with every problem listed, when:
- the file throws while it loads (an import that fails, a missing environment variable read at the top level), or has no default export;
- the default export is not an object, or has a key other than the four sections;
- an entry has an unknown key (with the closest valid one), a missing or malformed field, a pattern the server cannot match (no leading
/, a*restthat is not the last segment), atothat is not a path on this site or uses a capture the pattern does not define, a status other than 301, 302, 307 or 308, or an invalid header name or value; - a guard names no requirement (
requireSession: trueor arequireCookie), or itsredirectTois not a path on this site.
A path on this site starts with one /: //evil.example and /\evil.example are refused, because a browser reads them as links to another site. Send visitors to another site from a route handler.
/srv/shop/middleware.ts is invalid - no rule loads until every problem is fixed:
- guards[0] ("members/*rest"): path must start with "/"
- guards[1] ("/staff"): unknown key "require_session" - did you mean "requireSession"?
- guards[1] ("/staff"): names no requirement: set requireSession: true or requireCookieIn production the server then exits 1 with that error (see worker boot errors). In development it waits for you to save a fix; a file broken by a later edit makes the worker answer 503 - the last rules it loaded stay in force - until the next save fixes it. A requireSession guard denies every request while GIO_SESSION_SECRET is missing or invalid, and the server logs why.
Examples
Rules built from code
The file is a module: it can import data and read the environment, as long as the result is plain rules. It is evaluated once per worker start.
import { defineMiddleware } from '@gio.js/core';
import { MOVED_PAGES } from './lib/moved-pages';
export default defineMiddleware({
redirects: MOVED_PAGES.map(({ from, to }) => ({ from, to, status: 301 })),
headers: process.env.STAGING === '1'
? [{ path: '/*rest', headers: { 'x-robots-tag': 'noindex' } }]
: [],
});Protect a members area
import { defineMiddleware } from '@gio.js/core';
export default defineMiddleware({
guards: [
// Verified in Rust with GIO_SESSION_SECRET: signature and expiry of the gio_session cookie.
{ path: '/members/*rest', requireSession: true, redirectTo: '/login' },
],
});Good to know
- This is not Next.js middleware: there is no function that runs per request and no
NextResponse. Logic that must look at each request in JavaScript belongs ingetServerSideProps, a page action, aroute.ts, or anonRequestplugin in gio.config.ts. - Everything here can also be written in
gio.toml([[redirects]],[[rewrites]],[[headers]],[[guards]], with snake_case guard keys). Usemiddleware.tswhen the rules come from code or should be type-checked. - Like
gio.toml, a rule that cannot be enforced stops the worker at boot instead of being skipped, guards included (see Validation). - Rules see the canonical path: repeated and trailing slashes collapsed, escapes of unreserved characters decoded.
Related
- Middleware - the guide.
defineMiddleware[[redirects]],[[rewrites]],[[headers]],[[guards]]- Authentication
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | A file that throws while loading, or any rule that cannot be enforced as written (an invalid pattern, a malformed field, an unknown key, a target a browser reads as another site), stops the worker at boot; such rules used to be dropped with a warning, a guard's path left open. *rest also matches zero segments. Header rules also apply to redirect and guard responses. requireSession guards verify the session in Rust. |
v0.1.0-beta.6 | Introduced: redirects, rewrites, headers and cookie guards from a project-root middleware.ts. |