learning-website-nextjs1-2 Exercise 2: One App That Rewrites by Host ===================================================================== The other deployment shape: ONE Next.js app answers for every site. A proxy file runs before every request, reads the Host header, and rewrites the request to that site's folder inside the app. The visitor's address does not change. (In Next.js 16 this file is called proxy.ts; earlier versions called it middleware.ts, and the build output still labels it "Proxy (Middleware)".) Save as apps/multisite/proxy.ts: import { NextResponse, type NextRequest } from "next/server"; import { environment, siteForHost } from "@lw/sites"; /** * Runs before every request. It reads the Host header, decides which site the request is for, and REWRITES the * request to that site's folder inside this one app: languages.localhost/x is served by app/sites/languages/x, * and the visitor's address does not change. */ export function proxy(request: NextRequest) { const { pathname } = request.nextUrl; // /sites/... is the app's internal layout. A visitor must never reach it by typing it. if (pathname === "/sites" || pathname.startsWith("/sites/")) { return new NextResponse("Not found", { status: 404 }); } // Only the Host header is trusted. X-Forwarded-Host is sent by the client unless a proxy overwrites it, so it is ignored. const target = siteForHost(request.headers.get("host"), environment()); if (target === null) { return new NextResponse("Unknown site", { status: 404, headers: { "content-type": "text/plain" } }); } const url = request.nextUrl.clone(); url.pathname = `/sites/${target}${pathname === "/" ? "" : pathname}`; return NextResponse.rewrite(url); } export const config = { // everything except Next's own files (the compiled scripts and styles) and the icon matcher: ["/((?!_next/|favicon.ico).*)"], }; Save as apps/multisite/app/sites/[site]/page.tsx: import { notFound } from "next/navigation"; import { SITES, SITE_NAMES, environment, siteUrl, type SiteName } from "@lw/sites"; // every site (and the front page) is produced at build time; any other value is a 404 export const dynamicParams = false; export function generateStaticParams() { return [...SITE_NAMES, "portfolio"].map((site) => ({ site })); } export default async function SitePage({ params }: { params: Promise<{ site: string }> }) { const { site } = await params; const env = environment(); if (site === "portfolio") { return (

Philip’s Learning Notes

); } if (!(site in SITES)) notFound(); const name = site as SiteName; return (

{SITES[name].title}

Folders on this site: {SITES[name].folders.join(", ")}.

); } How it fits together: languages.localhost/ is rewritten to /sites/languages, which is a page that was built in advance (generateStaticParams lists the nine values, and dynamicParams = false makes any other value a 404). The other files (package.json, next.config.ts, tsconfig.json, app/layout.tsx) are those of the Chapter 1 apps with the name multisite and port 3010. Four decisions in proxy.ts, each tested below: 1. /sites/... is the app's INTERNAL layout, so a request that asks for it directly is a 404; otherwise languages.localhost/sites/systems would show another site's page on the wrong host. 2. Only the Host header is used. X-Forwarded-Host is sent by whoever makes the request unless a proxy overwrites it, so it is ignored (the same decision as in the Django project). 3. An unknown host is a 404 with no page, never a default site. 4. The matcher skips /_next/ (compiled scripts and styles) so those are served normally. npm run typecheck -w @lw/multisite passes npm run build -w @lw/multisite 11.2 s; 9 static pages (/sites/languages ... portfolio); "ƒ Proxy (Middleware)" npm run start -w @lw/multisite on port 3010 Requests sent to the running app (status, then the page's heading): languages.localhost (curl resolves the name itself) 200 Languages Host: webdevelopment.localhost:3010 200 Web Development Host: LANGUAGES.localhost (upper case) 200 Languages Host: localhost 200 Philip's Learning Notes Host: example.com 404 Host: evillanguages.localhost 404 languages host asking for /sites/systems 404 languages host asking for /sites 404 languages host, /nothing-here 404 X-Forwarded-Host: systems.localhost sent to languages 200 Languages (the header was ignored) no Host header at all (HTTP/1.0) 404 a file under /_next/static/ 200 application/javascript response header on a site page: x-middleware-rewrite: /sites/languages A headless Chrome opened http://languages.localhost:3010/ with no special settings and showed the Languages page: browsers resolve *.localhost to this machine themselves, so no hosts-file entries are needed. What was NOT checked: nothing was run behind Apache; the dynamic (not statically generated) case, where the rewritten page is produced per request, was not built; "next dev" was not used. WHY THIS WORKS AS AN ANSWER --------------------------- The table is the test: every row is a request that must behave in exactly one way, including the ones that try to break the rule (a made-up host, the internal path, a forged forwarded host).