Middleware
Declarative redirects, rewrites, response headers, and auth guards - executed in Rust before routing.
GioJS middleware is a set of declarative rules, not request-time JavaScript. You describe redirects, rewrites, response headers, and cookie guards; they are compiled once at load time and evaluated in the Rust HTTP layer on every request - before routing, before the cache, and before any Node code runs. Because the rules execute inside the server itself rather than in a separate step, there is no request header that skips them (contrast with Next.js middleware, where thex-middleware-subrequest header could bypass authorization checks entirely - CVE-2025-29927). A request either satisfies the rules or never reaches your pages.
Rules come from two places, and you can use either or both:
- gio.toml - static rules in your server config
- middleware.ts - a file at the project root (sibling of
app/)
gio.toml rules
[[redirects]]
from = "/old-home"
to = "/"
status = 301 # 301, 302, 307, or 308 - defaults to 302
[[redirects]]
from = "/blog/:slug"
to = "/posts/:slug"
[[rewrites]]
from = "/docs/*rest" # served under the requested URL
to = "/guide/*rest"
[[headers]]
path = "/docs/*rest"
[headers.headers]
x-frame-options = "DENY"
[[guards]]
path = "/admin/*rest"
require_cookie = "session"
redirect_to = "/login"middleware.ts
The same four rule kinds, typed. Export the result of defineMiddleware as the default export (guard fields are camelCase here):
// middleware.ts (project root, next to app/)
import { defineMiddleware } from '@gio.js/core';
export default defineMiddleware({
redirects: [
{ from: '/blog/:slug', to: '/posts/:slug', status: 301 },
],
rewrites: [
{ from: '/docs/*rest', to: '/guide/*rest' },
],
headers: [
{ path: '/admin/*rest', headers: { 'x-frame-options': 'DENY' } },
],
guards: [
{ path: '/admin/*rest', requireCookie: 'session', redirectTo: '/login' },
],
});These rules travel to the Rust server inside the worker's READY frame and refresh whenever the worker restarts. In development the watcher restarts the worker on changes under app/ and to gio.toml / gio.config.*, so middleware edits are picked up with the next restart. gio.toml rules are compiled once at server startup.
Pattern language
Patterns use the routing conventions and must start with /:
- Literal segments -
/aboutmatches exactly/about(a trailing slash on the request is tolerated) :param- captures one segment:/posts/:idmatches/posts/42but not/postsor/posts/a/b*rest- captures the entire remainder, slashes included:/docs/*restmatches/docs/a/b/c. It must be the last segment and requires at least one segment (/docsalone does not match)
Captures substitute into to targets by name, in any order:
[[redirects]]
from = "/u/:user/p/:post"
to = "/p/:post/by/:user" # /u/alice/p/42 -> /p/42/by/aliceEvery rule is validated when it is loaded, never at request time: a relative pattern, a catch-all in the middle, a to target referencing an unknown capture, a disallowed redirect status, or an invalid header name/value causes that rule to be skipped with a warning in the server log.
Evaluation order
Per request, the short-circuiting phases run in a fixed order:
- Guards
- Redirects
- Rewrites
Within each phase the first matching rule wins, and gio.toml rules are checked before middleware.ts rules. The phase order holds across both sources - a middleware.ts guard beats a gio.toml redirect on the same path.
The original query string is preserved verbatim: redirects append it to the Location header, and rewrites keep it on the rewritten URI. A rewrite changes the path that routing and the cache key see, while the browser URL stays what the client requested.
Guards
A guard redirects (302) any request to a matching path that does not carry a non-empty cookie of the given name - the request never reaches Node. It is a presence check, not validation: use it to keep anonymous traffic out of authenticated sections cheaply, and verify the session itself in getServerSideProps or a route handler.
Header rules
Header rules stamp response headers and do not short-circuit: every header rule whose path matches contributes its headers. They match the path the client requested (before any rewrite), and names/values are validated once at load time.
/_gio/* endpoints (health, metrics, image optimization, devtools) are exempt from all middleware rules.