From 7069d10a47dc0bf67cc46c67d236f3e7ba7040b4 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 15:29:34 +0000 Subject: [PATCH 01/20] chore: disable trailing slash rule --- vercel.json | 1 - 1 file changed, 1 deletion(-) diff --git a/vercel.json b/vercel.json index cb99d8f75..b4b2a9a97 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,5 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "trailingSlash": false, "redirects": [ { "source": "/(.*)", From 0a484dd02ed2d656fd96fed5ee2f3dc0b075f154 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 18:04:19 +0000 Subject: [PATCH 02/20] chore: enable trailing slash in nuxt config --- nuxt.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nuxt.config.ts b/nuxt.config.ts index e64c2a9ad..a23651cfb 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -86,6 +86,7 @@ export default defineNuxtConfig({ url: 'https://npmx.dev', name: 'npmx', description: 'A fast, modern browser for the npm registry', + trailingSlash: true, }, router: { From f471f07835ebf342a0dccc566b36e5ca9a8944cb Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 18:27:47 +0000 Subject: [PATCH 03/20] chore: enable trailing slash via middleware --- app/middleware/trailing-slash.global.ts | 6 ++---- nuxt.config.ts | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/middleware/trailing-slash.global.ts b/app/middleware/trailing-slash.global.ts index f0386e923..dee8aa6a5 100644 --- a/app/middleware/trailing-slash.global.ts +++ b/app/middleware/trailing-slash.global.ts @@ -8,12 +8,10 @@ * - /docs/getting-started/?query=value → /docs/getting-started?query=value */ export default defineNuxtRouteMiddleware(to => { - if (!import.meta.dev) return - - if (to.path !== '/' && to.path.endsWith('/')) { + if (to.path !== '' && !to.path.endsWith('/')) { return navigateTo( { - path: to.path.slice(0, -1), + path: to.path + '/', query: to.query, hash: to.hash, }, diff --git a/nuxt.config.ts b/nuxt.config.ts index a23651cfb..e64c2a9ad 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -86,7 +86,6 @@ export default defineNuxtConfig({ url: 'https://npmx.dev', name: 'npmx', description: 'A fast, modern browser for the npm registry', - trailingSlash: true, }, router: { From 28c6ff0ef4d02c556e5a8705248ae5b7ea8cb495 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 20:02:37 +0000 Subject: [PATCH 04/20] chore: improve trailing slash middleware --- app/middleware/trailing-slash.global.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/middleware/trailing-slash.global.ts b/app/middleware/trailing-slash.global.ts index dee8aa6a5..c666ea078 100644 --- a/app/middleware/trailing-slash.global.ts +++ b/app/middleware/trailing-slash.global.ts @@ -8,6 +8,13 @@ * - /docs/getting-started/?query=value → /docs/getting-started?query=value */ export default defineNuxtRouteMiddleware(to => { + if (import.meta.server) { + const event = useRequestEvent() + const url = event?.node.req.originalUrl || event?.node.req.url || '' + + if (url.includes('_payload')) return + } + if (to.path !== '' && !to.path.endsWith('/')) { return navigateTo( { From 63f5f8244e9e84004d04e532df9c9b712fd184f0 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 20:27:11 +0000 Subject: [PATCH 05/20] chore: improve version loading logic --- app/composables/npm/useResolvedVersion.ts | 24 +++++++++++------------ app/pages/package/[[org]]/[name].vue | 15 +++----------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/app/composables/npm/useResolvedVersion.ts b/app/composables/npm/useResolvedVersion.ts index 992ce62b7..98ee900c4 100644 --- a/app/composables/npm/useResolvedVersion.ts +++ b/app/composables/npm/useResolvedVersion.ts @@ -1,18 +1,18 @@ import type { ResolvedPackageVersion } from 'fast-npm-meta' -export function useResolvedVersion( +export async function useResolvedVersion( packageName: MaybeRefOrGetter, requestedVersion: MaybeRefOrGetter, ) { - return useFetch( - () => { - const version = toValue(requestedVersion) - return version - ? `https://npm.antfu.dev/${toValue(packageName)}@${version}` - : `https://npm.antfu.dev/${toValue(packageName)}` - }, - { - transform: (data: ResolvedPackageVersion) => data.version, - }, - ) + const versionData = useState('versionData') + + await callOnce(async () => { + const version = toValue(requestedVersion) + const name = toValue(packageName) + const url = version + ? `https://npm.antfu.dev/${name}@${version}` + : `https://npm.antfu.dev/${name}` + versionData.value = await $fetch(url).then(data => data.version) + }) + return versionData } diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index 1ded55a34..943aaa48f 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -194,18 +194,9 @@ const { data: skillsData } = useLazyFetch( const { data: packageAnalysis } = usePackageAnalysis(packageName, requestedVersion) const { data: moduleReplacement } = useModuleReplacement(packageName) -const { - data: resolvedVersion, - status: versionStatus, - error: versionError, -} = await useResolvedVersion(packageName, requestedVersion) - -if ( - versionStatus.value === 'error' && - versionError.value?.statusCode && - versionError.value.statusCode >= 400 && - versionError.value.statusCode < 500 -) { +const resolvedVersion = await useResolvedVersion(packageName, requestedVersion) + +if (resolvedVersion.value === null) { throw createError({ statusCode: 404, statusMessage: $t('package.not_found'), From 268874776b1bc0c1ea87f649882a7e9e0679cfcb Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 21:00:14 +0000 Subject: [PATCH 06/20] chore: disable fallback for packages --- nuxt.config.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index e64c2a9ad..378fbcb91 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -133,14 +133,14 @@ export default defineNuxtConfig({ }, }, // pages - '/package/:name': getISRConfig(60, { fallback: 'html' }), - '/package/:name/_payload.json': getISRConfig(60, { fallback: 'json' }), - '/package/:name/v/:version': getISRConfig(60, { fallback: 'html' }), - '/package/:name/v/:version/_payload.json': getISRConfig(60, { fallback: 'json' }), - '/package/:org/:name': getISRConfig(60, { fallback: 'html' }), - '/package/:org/:name/_payload.json': getISRConfig(60, { fallback: 'json' }), - '/package/:org/:name/v/:version': getISRConfig(60, { fallback: 'html' }), - '/package/:org/:name/v/:version/_payload.json': getISRConfig(60, { fallback: 'json' }), + '/package/:name': getISRConfig(60), + '/package/:name/_payload.json': getISRConfig(60), + '/package/:name/v/:version': getISRConfig(60), + '/package/:name/v/:version/_payload.json': getISRConfig(60), + '/package/:org/:name': getISRConfig(60), + '/package/:org/:name/_payload.json': getISRConfig(60), + '/package/:org/:name/v/:version': getISRConfig(60), + '/package/:org/:name/v/:version/_payload.json': getISRConfig(60), // infinite cache (versioned - doesn't change) '/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, '/package-docs/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, From b219252d1244eba7a8c65883111a714509520b17 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 21:34:48 +0000 Subject: [PATCH 07/20] chore: rewrite version resolver --- app/composables/npm/useResolvedVersion.ts | 26 ++++++++++++----------- app/pages/package/[[org]]/[name].vue | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/composables/npm/useResolvedVersion.ts b/app/composables/npm/useResolvedVersion.ts index 98ee900c4..fda018525 100644 --- a/app/composables/npm/useResolvedVersion.ts +++ b/app/composables/npm/useResolvedVersion.ts @@ -1,18 +1,20 @@ import type { ResolvedPackageVersion } from 'fast-npm-meta' -export async function useResolvedVersion( +export function useResolvedVersion( packageName: MaybeRefOrGetter, requestedVersion: MaybeRefOrGetter, ) { - const versionData = useState('versionData') - - await callOnce(async () => { - const version = toValue(requestedVersion) - const name = toValue(packageName) - const url = version - ? `https://npm.antfu.dev/${name}@${version}` - : `https://npm.antfu.dev/${name}` - versionData.value = await $fetch(url).then(data => data.version) - }) - return versionData + return useAsyncData( + () => `resolved-version:${toValue(packageName)}:${toValue(requestedVersion) ?? 'latest'}`, + async () => { + const version = toValue(requestedVersion) + const name = toValue(packageName) + const url = version + ? `https://npm.antfu.dev/${name}@${version}` + : `https://npm.antfu.dev/${name}` + const data = await $fetch(url) + return data.version + }, + { default: () => null }, + ) } diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index 943aaa48f..9a253d08d 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -194,7 +194,7 @@ const { data: skillsData } = useLazyFetch( const { data: packageAnalysis } = usePackageAnalysis(packageName, requestedVersion) const { data: moduleReplacement } = useModuleReplacement(packageName) -const resolvedVersion = await useResolvedVersion(packageName, requestedVersion) +const { data: resolvedVersion } = await useResolvedVersion(packageName, requestedVersion) if (resolvedVersion.value === null) { throw createError({ From 9adacac4c0df130d0fafe3e3da8ddccfee2eac13 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 21:35:19 +0000 Subject: [PATCH 08/20] chore: disable extra caching for payload routes --- nuxt.config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index 378fbcb91..a381f5b27 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -134,13 +134,9 @@ export default defineNuxtConfig({ }, // pages '/package/:name': getISRConfig(60), - '/package/:name/_payload.json': getISRConfig(60), '/package/:name/v/:version': getISRConfig(60), - '/package/:name/v/:version/_payload.json': getISRConfig(60), '/package/:org/:name': getISRConfig(60), - '/package/:org/:name/_payload.json': getISRConfig(60), '/package/:org/:name/v/:version': getISRConfig(60), - '/package/:org/:name/v/:version/_payload.json': getISRConfig(60), // infinite cache (versioned - doesn't change) '/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, '/package-docs/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, From 71fe94da0bb9e38065653455cb68360518cbd1b0 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sun, 22 Feb 2026 23:12:13 +0000 Subject: [PATCH 09/20] fix: configure trailing slash for all links --- app/components/Code/DirectoryListing.vue | 1 + app/components/Code/FileTree.vue | 1 + app/components/DependencyPathPopup.vue | 1 + app/components/Header/MobileMenu.client.vue | 1 + app/components/Header/OrgsDropdown.vue | 3 +++ app/components/Header/PackagesDropdown.vue | 3 +++ app/components/Link/Base.vue | 5 ++++- app/components/Org/MembersPanel.vue | 1 + app/components/Org/TeamsPanel.vue | 1 + app/components/Package/Card.vue | 1 + app/components/Package/ClaimPackageModal.vue | 2 ++ app/components/Package/DeprecatedTree.vue | 1 + app/components/Package/TableRow.vue | 2 ++ app/components/Package/VulnerabilityTree.vue | 2 ++ app/components/SearchSuggestionCard.vue | 1 + app/components/Terminal/Install.vue | 2 ++ app/components/VersionSelector.vue | 3 +++ app/middleware/trailing-slash.global.ts | 2 ++ app/pages/accessibility.vue | 1 + .../[[org]]/[packageName]/v/[version]/[...filePath].vue | 3 +++ app/pages/package-docs/[...path].vue | 2 ++ app/pages/privacy.vue | 2 ++ app/pages/~[username]/orgs.vue | 2 ++ nuxt.config.ts | 1 + 24 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/components/Code/DirectoryListing.vue b/app/components/Code/DirectoryListing.vue index 31a6d1fe2..21ee58438 100644 --- a/app/components/Code/DirectoryListing.vue +++ b/app/components/Code/DirectoryListing.vue @@ -103,6 +103,7 @@ const bytesFormatter = useBytesFormatter() >