From 40417fbc7569028b338a13f34c3d21f574db30ad Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 27 Jan 2026 02:45:26 +0000 Subject: [PATCH 1/6] perf: use vue router to parse package page paths --- app/components/PackageCard.vue | 2 +- app/components/PackageDependencies.vue | 10 ++-- app/components/PackageInstallScripts.vue | 2 +- app/components/PackageVersions.vue | 2 +- app/composables/usePackageRoute.ts | 5 ++ app/pages/[...package].vue | 68 +++++++----------------- app/pages/code/[...path].vue | 7 ++- app/pages/index.vue | 2 +- 8 files changed, 37 insertions(+), 61 deletions(-) create mode 100644 app/composables/usePackageRoute.ts diff --git a/app/components/PackageCard.vue b/app/components/PackageCard.vue index 5d6945d14..dc6498698 100644 --- a/app/components/PackageCard.vue +++ b/app/components/PackageCard.vue @@ -27,7 +27,7 @@ const emit = defineEmits<{ class="font-mono text-sm sm:text-base font-medium text-fg group-hover:text-fg transition-colors duration-200 min-w-0 break-all" > { class="flex items-center justify-between py-1 text-sm gap-2" > {{ dep }} @@ -75,7 +75,7 @@ const sortedOptionalDependencies = computed(() => { { >
{{ peer.name }} @@ -170,13 +170,13 @@ const sortedOptionalDependencies = computed(() => { class="flex items-center justify-between py-1 text-sm gap-2" > {{ dep }} diff --git a/app/components/PackageInstallScripts.vue b/app/components/PackageInstallScripts.vue index c1c957902..0cd9338e8 100644 --- a/app/components/PackageInstallScripts.vue +++ b/app/components/PackageInstallScripts.vue @@ -69,7 +69,7 @@ const isExpanded = shallowRef(false) class="flex items-center justify-between py-0.5 text-sm gap-2" > {{ dep }} diff --git a/app/components/PackageVersions.vue b/app/components/PackageVersions.vue index afdbe961c..e41604df0 100644 --- a/app/components/PackageVersions.vue +++ b/app/components/PackageVersions.vue @@ -40,7 +40,7 @@ function hasProvenance(version: PackumentVersion | undefined): boolean { function versionRoute(version: string): RouteLocationRaw { return { name: 'package', - params: { package: [...props.packageName.split('/'), 'v', version] }, + params: { ...parsePackageRouteParams(props.packageName), version }, } } diff --git a/app/composables/usePackageRoute.ts b/app/composables/usePackageRoute.ts new file mode 100644 index 000000000..1f6b92924 --- /dev/null +++ b/app/composables/usePackageRoute.ts @@ -0,0 +1,5 @@ +export function parsePackageRouteParams(pkg: string) { + const [org, name] = pkg.startsWith('@') ? pkg.split('/') : [null, pkg] + + return { org, name } +} diff --git a/app/pages/[...package].vue b/app/pages/[...package].vue index 262801871..00ba91e21 100644 --- a/app/pages/[...package].vue +++ b/app/pages/[...package].vue @@ -8,65 +8,37 @@ import { areUrlsEquivalent } from '#shared/utils/url' definePageMeta({ name: 'package', - alias: ['/package/:package(.*)*'], + /** + * Supported patterns: + * /nuxt → packageName: "nuxt", requestedVersion: null + * /nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0" + * /@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null + * /@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" + * /axios@1.13.3 → packageName: "axios", requestedVersion: "1.13.3" + * /@nuxt/kit@1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" + */ + path: '/:org(@[^/]+/)?:name([^@/]+):version()?', + alias: [ + '/:org(@[^/]+/)?:name([^@/]+)/v/:version()?', + '/package/:org(@[^/]+/)?:name([^@/]+):version()?', + '/package/:org(@[^/]+/)?:name([^@/]+)/v/:version()?', + ], }) const route = useRoute('package') const router = useRouter() -// Parse package name and optional version from URL -// Patterns: -// /nuxt → packageName: "nuxt", requestedVersion: null -// /nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0" -// /@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null -// /@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" -// /axios@1.13.3 → packageName: "axios", requestedVersion: "1.13.3" -// /@nuxt/kit@1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" -const parsedRoute = computed(() => { - const segments = route.params.package || [] - - // Find the /v/ separator for version - const vIndex = segments.indexOf('v') - if (vIndex !== -1 && vIndex < segments.length - 1) { - return { - packageName: segments.slice(0, vIndex).join('/'), - requestedVersion: segments.slice(vIndex + 1).join('/'), - } - } - - // Parse @ versioned package - const fullPath = segments.join('/') - const versionMatch = fullPath.match(/^(@[^/]+\/[^/]+|[^/]+)@([^/]+)$/) - if (versionMatch) { - const [, packageName, requestedVersion] = versionMatch as [string, string, string] - return { - packageName, - requestedVersion, - } - } - - return { - packageName: fullPath, - requestedVersion: null as string | null, - } -}) - -const packageName = computed(() => parsedRoute.value.packageName) -const requestedVersion = computed(() => parsedRoute.value.requestedVersion) +const orgName = computed(() => route.params.org) +const requestedVersion = computed(() => route.params.version || null) +const packageName = computed(() => + orgName.value ? `${orgName.value}/${route.params.name}` : route.params.name, +) if (import.meta.server) { assertValidPackageName(packageName.value) } -// Extract org name from scoped package (e.g., "@nuxt/kit" -> "nuxt") -const orgName = computed(() => { - const name = packageName.value - if (!name.startsWith('@')) return null - const match = name.match(/^@([^/]+)\//) - return match ? match[1] : null -}) - const { data: pkg, status, error, resolvedVersion } = usePackage(packageName, requestedVersion) const { data: downloads } = usePackageDownloads(packageName, 'last-week') diff --git a/app/pages/code/[...path].vue b/app/pages/code/[...path].vue index dd24a6c3d..574dd4e3e 100644 --- a/app/pages/code/[...path].vue +++ b/app/pages/code/[...path].vue @@ -220,11 +220,10 @@ const orgName = computed(() => { // Build route object for package link (with optional version) function packageRoute(ver?: string | null) { - const segments = packageName.value.split('/') - if (ver) { - segments.push('v', ver) + return { + name: 'package' as const, + params: { ...parsePackageRouteParams(packageName.value), version: ver }, } - return { name: 'package' as const, params: { package: segments } } } // Format file size diff --git a/app/pages/index.vue b/app/pages/index.vue index 2c25c1931..b36685f09 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -96,7 +96,7 @@ defineOgImageComponent('Default') :key="pkg" > Date: Tue, 27 Jan 2026 02:49:49 +0000 Subject: [PATCH 2/6] fix: use path here too --- app/components/PackageDependencies.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/PackageDependencies.vue b/app/components/PackageDependencies.vue index 193b4499b..5172d07ae 100644 --- a/app/components/PackageDependencies.vue +++ b/app/components/PackageDependencies.vue @@ -130,7 +130,7 @@ const sortedOptionalDependencies = computed(() => { Date: Tue, 27 Jan 2026 02:53:46 +0000 Subject: [PATCH 3/6] refactor: simplify --- app/components/PackageCard.vue | 2 +- app/components/PackageDependencies.vue | 12 ++++++------ app/components/PackageInstallScripts.vue | 2 +- app/components/PackageVersions.vue | 2 +- app/composables/usePackageRoute.ts | 4 ++-- app/pages/code/[...path].vue | 2 +- app/pages/index.vue | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/components/PackageCard.vue b/app/components/PackageCard.vue index dc6498698..3466b668b 100644 --- a/app/components/PackageCard.vue +++ b/app/components/PackageCard.vue @@ -27,7 +27,7 @@ const emit = defineEmits<{ class="font-mono text-sm sm:text-base font-medium text-fg group-hover:text-fg transition-colors duration-200 min-w-0 break-all" > { class="flex items-center justify-between py-1 text-sm gap-2" > {{ dep }} @@ -75,7 +75,7 @@ const sortedOptionalDependencies = computed(() => { { >
{{ peer.name }} @@ -130,7 +130,7 @@ const sortedOptionalDependencies = computed(() => { { class="flex items-center justify-between py-1 text-sm gap-2" > {{ dep }} diff --git a/app/components/PackageInstallScripts.vue b/app/components/PackageInstallScripts.vue index 0cd9338e8..af95f01f3 100644 --- a/app/components/PackageInstallScripts.vue +++ b/app/components/PackageInstallScripts.vue @@ -69,7 +69,7 @@ const isExpanded = shallowRef(false) class="flex items-center justify-between py-0.5 text-sm gap-2" > {{ dep }} diff --git a/app/components/PackageVersions.vue b/app/components/PackageVersions.vue index e41604df0..39e999019 100644 --- a/app/components/PackageVersions.vue +++ b/app/components/PackageVersions.vue @@ -40,7 +40,7 @@ function hasProvenance(version: PackumentVersion | undefined): boolean { function versionRoute(version: string): RouteLocationRaw { return { name: 'package', - params: { ...parsePackageRouteParams(props.packageName), version }, + params: getPackagePageParams(props.packageName, version), } } diff --git a/app/composables/usePackageRoute.ts b/app/composables/usePackageRoute.ts index 1f6b92924..5f4b53f00 100644 --- a/app/composables/usePackageRoute.ts +++ b/app/composables/usePackageRoute.ts @@ -1,5 +1,5 @@ -export function parsePackageRouteParams(pkg: string) { +export function getPackagePageParams(pkg: string, version: string | null = null) { const [org, name] = pkg.startsWith('@') ? pkg.split('/') : [null, pkg] - return { org, name } + return { org, name, version } } diff --git a/app/pages/code/[...path].vue b/app/pages/code/[...path].vue index 574dd4e3e..cfb8f0d83 100644 --- a/app/pages/code/[...path].vue +++ b/app/pages/code/[...path].vue @@ -222,7 +222,7 @@ const orgName = computed(() => { function packageRoute(ver?: string | null) { return { name: 'package' as const, - params: { ...parsePackageRouteParams(packageName.value), version: ver }, + params: getPackagePageParams(packageName.value, ver), } } diff --git a/app/pages/index.vue b/app/pages/index.vue index b36685f09..688d9730f 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -96,7 +96,7 @@ defineOgImageComponent('Default') :key="pkg" > Date: Tue, 27 Jan 2026 09:35:59 +0000 Subject: [PATCH 4/6] refactor: use module to inject double route --- app/components/PackageCard.vue | 2 +- app/components/PackageDependencies.vue | 15 ++++---- app/components/PackageInstallScripts.vue | 2 +- app/components/PackageVersions.vue | 5 +-- app/composables/usePackageRoute.ts | 16 +++++++-- .../{[...package].vue => [[org]]/[name].vue} | 20 ++--------- app/pages/code/[...path].vue | 5 +-- app/pages/index.vue | 2 +- modules/routing.ts | 34 +++++++++++++++++++ 9 files changed, 62 insertions(+), 39 deletions(-) rename app/pages/{[...package].vue => [[org]]/[name].vue} (97%) create mode 100644 modules/routing.ts diff --git a/app/components/PackageCard.vue b/app/components/PackageCard.vue index 3466b668b..e33018739 100644 --- a/app/components/PackageCard.vue +++ b/app/components/PackageCard.vue @@ -27,7 +27,7 @@ const emit = defineEmits<{ class="font-mono text-sm sm:text-base font-medium text-fg group-hover:text-fg transition-colors duration-200 min-w-0 break-all" > { class="flex items-center justify-between py-1 text-sm gap-2" > {{ dep }} @@ -75,7 +75,7 @@ const sortedOptionalDependencies = computed(() => { { >
{{ peer.name }} @@ -128,10 +128,7 @@ const sortedOptionalDependencies = computed(() => {
@@ -170,13 +167,13 @@ const sortedOptionalDependencies = computed(() => { class="flex items-center justify-between py-1 text-sm gap-2" > {{ dep }} diff --git a/app/components/PackageInstallScripts.vue b/app/components/PackageInstallScripts.vue index af95f01f3..81b76822f 100644 --- a/app/components/PackageInstallScripts.vue +++ b/app/components/PackageInstallScripts.vue @@ -69,7 +69,7 @@ const isExpanded = shallowRef(false) class="flex items-center justify-between py-0.5 text-sm gap-2" > {{ dep }} diff --git a/app/components/PackageVersions.vue b/app/components/PackageVersions.vue index 39e999019..7e8b37656 100644 --- a/app/components/PackageVersions.vue +++ b/app/components/PackageVersions.vue @@ -38,10 +38,7 @@ function hasProvenance(version: PackumentVersion | undefined): boolean { // Build route object for package version link function versionRoute(version: string): RouteLocationRaw { - return { - name: 'package', - params: getPackagePageParams(props.packageName, version), - } + return getPackageRoute(props.packageName, version) } // Version to tags lookup (supports multiple tags per version) diff --git a/app/composables/usePackageRoute.ts b/app/composables/usePackageRoute.ts index 5f4b53f00..153df7d6b 100644 --- a/app/composables/usePackageRoute.ts +++ b/app/composables/usePackageRoute.ts @@ -1,5 +1,17 @@ -export function getPackagePageParams(pkg: string, version: string | null = null) { +export function getPackageRoute(pkg: string, version: string | null = null) { const [org, name] = pkg.startsWith('@') ? pkg.split('/') : [null, pkg] + if (version) { + return { + name: 'package-version', + params: { org, name, version }, + } as const + } - return { org, name, version } + return { + name: 'package', + params: { + org, + name, + }, + } as const } diff --git a/app/pages/[...package].vue b/app/pages/[[org]]/[name].vue similarity index 97% rename from app/pages/[...package].vue rename to app/pages/[[org]]/[name].vue index 00ba91e21..ac94b07cf 100644 --- a/app/pages/[...package].vue +++ b/app/pages/[[org]]/[name].vue @@ -8,25 +8,11 @@ import { areUrlsEquivalent } from '#shared/utils/url' definePageMeta({ name: 'package', - /** - * Supported patterns: - * /nuxt → packageName: "nuxt", requestedVersion: null - * /nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0" - * /@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null - * /@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" - * /axios@1.13.3 → packageName: "axios", requestedVersion: "1.13.3" - * /@nuxt/kit@1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" - */ - path: '/:org(@[^/]+/)?:name([^@/]+):version()?', - alias: [ - '/:org(@[^/]+/)?:name([^@/]+)/v/:version()?', - '/package/:org(@[^/]+/)?:name([^@/]+):version()?', - '/package/:org(@[^/]+/)?:name([^@/]+)/v/:version()?', - ], }) -const route = useRoute('package') - +// the syntax for matching is complex, so we implement in modules/routing.ts +// which injects a second identical route with required versions +const route = useRoute('package-version') const router = useRouter() const orgName = computed(() => route.params.org) diff --git a/app/pages/code/[...path].vue b/app/pages/code/[...path].vue index cfb8f0d83..dd4873076 100644 --- a/app/pages/code/[...path].vue +++ b/app/pages/code/[...path].vue @@ -220,10 +220,7 @@ const orgName = computed(() => { // Build route object for package link (with optional version) function packageRoute(ver?: string | null) { - return { - name: 'package' as const, - params: getPackagePageParams(packageName.value, ver), - } + return getPackageRoute(packageName.value, ver) } // Format file size diff --git a/app/pages/index.vue b/app/pages/index.vue index 688d9730f..573feba53 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -96,7 +96,7 @@ defineOgImageComponent('Default') :key="pkg" > { + const packagePage = pages.find(page => page.name === 'package') + if (packagePage) { + packagePage.path = '/:org(@[^/]+)?/:name' + packagePage.alias = ['/package/:org(@[^/]+)?/:name'] + } + pages.push({ + ...packagePage, + name: 'package-version', + path: '/:org(@[^/]+)?/:name/v/:version', + alias: ['/:org(@[^/]+)?/:name@:version', '/package/:org(@[^/]+)?/:name/v/:version'], + }) + }) + }, +}) From 8dd9040a2bfc667aec8d540994aace59e082304f Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 29 Jan 2026 13:57:44 +0000 Subject: [PATCH 5/6] refactor: revert changes to path matching --- app/components/ClaimPackageModal.vue | 4 +- app/components/HeaderPackagesDropdown.vue | 2 +- app/components/VersionSelector.vue | 2 +- app/composables/usePackageRoute.ts | 53 ++++++++++++------- .../{[[org]]/[name].vue => [...package].vue} | 17 ++++-- modules/routing.ts | 34 ------------ 6 files changed, 51 insertions(+), 61 deletions(-) rename app/pages/{[[org]]/[name].vue => [...package].vue} (99%) delete mode 100644 modules/routing.ts diff --git a/app/components/ClaimPackageModal.vue b/app/components/ClaimPackageModal.vue index 60cfaa3f3..926eafde2 100644 --- a/app/components/ClaimPackageModal.vue +++ b/app/components/ClaimPackageModal.vue @@ -192,7 +192,7 @@ const connectorModalOpen = shallowRef(false)
@@ -299,7 +299,7 @@ const connectorModalOpen = shallowRef(false)
diff --git a/app/components/HeaderPackagesDropdown.vue b/app/components/HeaderPackagesDropdown.vue index 4c6a09c37..2ed367e0d 100644 --- a/app/components/HeaderPackagesDropdown.vue +++ b/app/components/HeaderPackagesDropdown.vue @@ -94,7 +94,7 @@ function handleKeydown(event: KeyboardEvent) {
  • {{ pkg }} diff --git a/app/components/VersionSelector.vue b/app/components/VersionSelector.vue index c8f67e3db..e159dd50a 100644 --- a/app/components/VersionSelector.vue +++ b/app/components/VersionSelector.vue @@ -625,7 +625,7 @@ watch(
    diff --git a/app/composables/usePackageRoute.ts b/app/composables/usePackageRoute.ts index b9b25f4d7..74fa770a8 100644 --- a/app/composables/usePackageRoute.ts +++ b/app/composables/usePackageRoute.ts @@ -6,19 +6,12 @@ * @returns Route object with name and params */ export function getPackageRoute(pkg: string, version: string | null = null) { - const [org, name] = pkg.startsWith('@') ? pkg.split('/') : [null, pkg] - if (version) { - return { - name: 'package-version', - params: { org, name, version }, - } as const - } - return { name: 'package', params: { - org, - name, + package: [...pkg.split('/'), version ? 'v' : null, version].filter( + (a): a is NonNullable => !!a, + ), }, } as const } @@ -36,17 +29,39 @@ export function getPackageRoute(pkg: string, version: string | null = null) { * @public */ export function usePackageRoute() { - const route = useRoute('package-version') + const route = useRoute('package') + + const data = computed(() => { + const segments = route.params.package || [] - const orgName = computed(() => route.params.org) - const requestedVersion = computed(() => route.params.version || null) - const packageName = computed(() => - orgName.value ? `${orgName.value}/${route.params.name}` : route.params.name, - ) + // Find the /v/ separator for version + const vIndex = segments.indexOf('v') + if (vIndex !== -1 && vIndex < segments.length - 1) { + return { + packageName: segments.slice(0, vIndex).join('/'), + requestedVersion: segments.slice(vIndex + 1).join('/'), + } + } + + // Parse @ versioned package + const fullPath = segments.join('/') + const versionMatch = fullPath.match(/^(@[^/]+\/[^/]+|[^/]+)@([^/]+)$/) + if (versionMatch) { + const [, packageName, requestedVersion] = versionMatch as [string, string, string] + return { + packageName, + requestedVersion, + } + } + + return { + packageName: fullPath, + requestedVersion: null as string | null, + } + }) return { - packageName, - requestedVersion, - orgName, + packageName: computed(() => data.value.packageName), + requestedVersion: computed(() => data.value.requestedVersion), } } diff --git a/app/pages/[[org]]/[name].vue b/app/pages/[...package].vue similarity index 99% rename from app/pages/[[org]]/[name].vue rename to app/pages/[...package].vue index 6780c1846..026131f4f 100644 --- a/app/pages/[[org]]/[name].vue +++ b/app/pages/[...package].vue @@ -8,11 +8,20 @@ import { areUrlsEquivalent } from '#shared/utils/url' definePageMeta({ name: 'package', + alias: ['/package/:package(.*)*'], }) const router = useRouter() -const { packageName, requestedVersion, orgName } = usePackageRoute() +const { packageName, requestedVersion } = usePackageRoute() + +const orgName = computed(() => { + const name = packageName.value + if (!name.startsWith('@')) return null + + const match = name.match(/^@([^/]+)\//) + return match ? match[1] : null +}) if (import.meta.server) { assertValidPackageName(packageName.value) @@ -471,7 +480,7 @@ defineOgImageComponent('Package', { {{ displayVersion.version }} @@ -992,7 +1001,7 @@ defineOgImageComponent('Package', { > @@ -1076,7 +1085,7 @@ defineOgImageComponent('Package', { }} diff --git a/modules/routing.ts b/modules/routing.ts deleted file mode 100644 index 79ec89951..000000000 --- a/modules/routing.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { defineNuxtModule, useNuxt } from 'nuxt/kit' - -export default defineNuxtModule({ - meta: { - name: 'routing-extensions', - }, - setup() { - const nuxt = useNuxt() - - /** - * Supported patterns: - * /nuxt → packageName: "nuxt", requestedVersion: null - * /nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0" - * /@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null - * /@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" - * /axios@1.13.3 → packageName: "axios", requestedVersion: "1.13.3" - * /@nuxt/kit@1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" - */ - - nuxt.hook('pages:resolved', pages => { - const packagePage = pages.find(page => page.name === 'package') - if (packagePage) { - packagePage.path = '/:org(@[^/]+)?/:name' - packagePage.alias = ['/package/:org(@[^/]+)?/:name'] - } - pages.push({ - ...packagePage, - name: 'package-version', - path: '/:org(@[^/]+)?/:name/v/:version', - alias: ['/:org(@[^/]+)?/:name@:version', '/package/:org(@[^/]+)?/:name/v/:version'], - }) - }) - }, -}) From e7f223a8637e04ceb4118d40d3f1b0776492bd2a Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 29 Jan 2026 14:05:07 +0000 Subject: [PATCH 6/6] chore: ignore `getPackageRoute` --- app/composables/usePackageRoute.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/composables/usePackageRoute.ts b/app/composables/usePackageRoute.ts index 74fa770a8..8b991daf4 100644 --- a/app/composables/usePackageRoute.ts +++ b/app/composables/usePackageRoute.ts @@ -4,6 +4,7 @@ * @param pkg - Package name (e.g., "nuxt" or "@nuxt/kit") * @param version - Optional version string * @returns Route object with name and params + * @public */ export function getPackageRoute(pkg: string, version: string | null = null) { return {