[ HTTP & responses ]
@sdxc/no-www-middleware
Router middleware that permanently redirects a www. hostname to the apex domain
- Used by
- blog
Router middleware that permanently redirects a www. hostname to the apex domain, so a site answers on one host for search engines and for the cookies scoped to it.
Installation
npm add @sdxc/no-www-middleware
It runs on a remix/router middleware chain, so remix (v3) installs alongside it.
Usage
Redirect to the apex domain
import { noWWW } from "@sdxc/no-www-middleware";
import { createRouter } from "remix/router";
let router = createRouter({ middleware: [noWWW()] });
// GET https://www.example.com/posts?page=2 -> 308, Location: https://example.com/posts?page=2
// GET https://example.com/posts -> the matched handler
// GET https://blog.example.com/ -> the matched handler
The longhand
noWWW() stands in for this hand-written middleware:
import type { Middleware } from "remix/router";
let stripWWW: Middleware = (context, next) => {
let url = new URL(context.request.url);
if (!url.hostname.startsWith("www.")) return next();
url.hostname = url.hostname.slice(4);
return new Response(null, { status: 308, headers: { Location: url.href } });
};
API
noWWW(): Middleware
Returns a middleware that answers a request whose hostname starts with the www. label with a 308 to the same URL minus that one label, keeping scheme, port, path and query string; every other request goes to next(). The hostname is matched as the URL parser lowercases it, so WWW.Example.com redirects to example.com, while blog.www.example.com and wwwexample.com pass through.
Pattern: Canonical Host Ahead of Other Middleware
Put it at the top of the chain, before sessions, authentication and body parsing, so a redirected request skips work its retry on the apex domain repeats. Pair it with @sdxc/trailing-slash-middleware to settle the whole canonical URL.
import { noWWW } from "@sdxc/no-www-middleware";
import { trailingSlash } from "@sdxc/trailing-slash-middleware";
import { createRouter } from "remix/router";
let router = createRouter({ middleware: [noWWW(), trailingSlash()] });
router.get("/posts", () => new Response("posts"));
The www. host has to reach the same Worker for the redirect to run, so route both hostnames to it. Browsers cache a 308, so keep the apex domain canonical once it is live.
Pattern: Form Posts Through the Redirect
A 308 makes the client repeat the method and body, so a form that posts to the www. host still arrives as a POST on the apex domain.
import { noWWW } from "@sdxc/no-www-middleware";
import { createRouter } from "remix/router";
let router = createRouter({ middleware: [noWWW()] });
router.post("/comments", async ({ request }) => {
let form = await request.formData();
return Response.json({ text: form.get("text") });
});
// POST https://www.example.com/comments -> 308, Location: https://example.com/comments
// the browser re-sends the POST and its body to https://example.com/comments
Pattern: Observe Redirects From an Outer Middleware
A middleware above noWWW() receives the redirect like any other response, so it can log it or add headers.
import type { Middleware } from "remix/router";
import { noWWW } from "@sdxc/no-www-middleware";
import { createRouter } from "remix/router";
let poweredBy: Middleware = async (_context, next) => {
let response = await next();
response.headers.set("X-Powered-By", "example");
return response;
};
let router = createRouter({ middleware: [poweredBy, noWWW()] });