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 5d6945d1..dc649869 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 c1c95790..0cd9338e 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 afdbe961..e41604df 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 00000000..1f6b9292 --- /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 26280187..00ba91e2 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 dd24a6c3..574dd4e3 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 2c25c193..b36685f0 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 193b4499..5172d07a 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 dc649869..3466b668 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 0cd9338e..af95f01f 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 e41604df..39e99901 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 1f6b9292..5f4b53f0 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 574dd4e3..cfb8f0d8 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 b36685f0..688d9730 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 3466b668..e3301873 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 af95f01f..81b76822 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 39e99901..7e8b3765 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 5f4b53f0..153df7d6 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 00ba91e2..ac94b07c 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 cfb8f0d8..dd487307 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 688d9730..573feba5 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 60cfaa3f..926eafde 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 4c6a09c3..2ed367e0 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 c8f67e3d..e159dd50 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 b9b25f4d..74fa770a 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 6780c184..026131f4 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 79ec8995..00000000 --- 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 74fa770a..8b991daf 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 {