Security
Middleware is not authorization
Four NestJS advisories in seven months, all one bug: the matcher and the handler disagreed about the request. The check belongs where the data is read.
We found this on our own site, which is why we can describe it honestly.
Next.js App Router merges metadata down the route tree, key by key. A layout exports some metadata, a page exports some more, and the framework combines them — page values win where both define a key, and layout values survive where the page is silent. That is the documented behaviour and it is convenient right up until the key in question is alternates.
Our locale layout set alternates for the homepage, because the homepage is what the locale layout renders at /ar. Every page below it that did not export its own alternates inherited that object wholesale. So /ar/pricing, /ar/services, all nine /ar/services/[slug] pages and their English counterparts each shipped:
<link rel="canonical" href="https://qasioun.cloud/ar">Twenty-four URLs, each telling Google it was a duplicate of the homepage and should not be indexed on its own. Nothing was broken. Every page rendered correctly, returned 200, and appeared in the sitemap. The site was quietly asking to be de-indexed.
A wrong canonical produces no error, no warning and no visual symptom. It fails in a system you do not control, on a schedule you do not see, and the feedback is a gradual absence of traffic for pages that were never ranked in the first place — which looks exactly like a new site being new.
The hreflang half is worse. An inherited language map does not merely fail to help, it makes a positive false claim: it tells a search engine that the Arabic alternate of /en/pricing is the Arabic homepage. A wrong hreflang is worse than no hreflang, because no hreflang leaves the engine to guess and a wrong one stops it guessing.
The rule we now hold: every indexable route calls alternates() in its own generateMetadata. Not "should" — a route that does not is a bug, because metadata inheritance means omission is not neutral.
The second half of the fix matters more than the first. The canonical and the language map are built by one function, from the unprefixed path:
export function alternates(path: string, locale: Locale) {
const href = (l: Locale) => `/${l}${path === '/' ? '' : path}`;
const languages = Object.fromEntries(locales.map((l) => [l, href(l)]));
languages['x-default'] = href(defaultLocale);
return {canonical: href(locale), languages};
}Hand-writing the alternates per page would have fixed the twenty-four pages and guaranteed the same bug returns the day a third locale is added. Deriving every spelling of the URL from one place is what makes them unable to disagree — including the trailing slash on a locale home, where /ar and /ar/ are two spellings of one page and the canonical exists precisely to pick one.
hreflang points at a URL that returns 200 and is itself canonical. A chain of canonical to redirect to canonical is a wasted signal.x-default, and point it at the locale you actually serve to everyone else.Security
Four NestJS advisories in seven months, all one bug: the matcher and the handler disagreed about the request. The check belongs where the data is read.
Security
One RCE at CVSS 10.0, then six denial-of-service patches in eight months. When the contract is "accept whatever the format encodes", fixing one lever leaves the rest.
Security
Eighteen malicious versions of one package went out in two and a half hours, harvesting tokens on install. Lockfiles, disabled scripts and short-lived credentials.