22import { joinURL } from ' ufo'
33import type { PackumentVersion , NpmVersionDist } from ' #shared/types'
44
5- const route = useRoute (' package-name' )
5+ definePageMeta ({
6+ name: ' package' ,
7+ alias: [' /package/:package(.*)*' ],
8+ })
9+
10+ const route = useRoute (' package' )
611
712// Parse package name and optional version from URL
813// Patterns:
9- // /package/ nuxt → packageName: "nuxt", requestedVersion: null
10- // /package/ nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0"
11- // /package/ @nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null
12- // /package/ @nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0"
14+ // /nuxt → packageName: "nuxt", requestedVersion: null
15+ // /nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0"
16+ // /@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null
17+ // /@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0"
1318const parsedRoute = computed (() => {
14- const segments = Array . isArray ( route .params .name ) ? route . params . name : [ route . params . name ?? ' ' ]
19+ const segments = route .params .package || [ ]
1520
1621 // Find the /v/ separator for version
1722 const vIndex = segments .indexOf (' v' )
@@ -220,6 +225,16 @@ onMounted(() => {
220225 nextTick (checkDescriptionOverflow )
221226})
222227
228+ // Canonical URL for this package page
229+ const canonicalUrl = computed (() => {
230+ const base = ` https://npmx.dev/${packageName .value } `
231+ return requestedVersion .value ? ` ${base }/v/${requestedVersion .value } ` : base
232+ })
233+
234+ useHead ({
235+ link: [{ rel: ' canonical' , href: canonicalUrl }],
236+ })
237+
223238useSeoMeta ({
224239 title : () => (pkg .value ?.name ? ` ${pkg .value .name } - npmx ` : ' Package - npmx' ),
225240 description : () => pkg .value ?.description ?? ' ' ,
@@ -246,7 +261,7 @@ defineOgImageComponent('Package', {
246261 <h1 class =" font-mono text-2xl sm:text-3xl font-medium" >
247262 <NuxtLink
248263 v-if =" orgName"
249- :to =" `/ org/${ orgName}` "
264+ :to =" { name: ' org', params: { org: orgName } } "
250265 class =" text-fg-muted hover:text-fg transition-colors duration-200"
251266 >@{{ orgName }}</NuxtLink
252267 ><span v-if =" orgName" >/</span
@@ -432,7 +447,10 @@ defineOgImageComponent('Package', {
432447 </li >
433448 <li v-if =" displayVersion" >
434449 <NuxtLink
435- :to =" `/package/code/${pkg.name}/v/${displayVersion.version}`"
450+ :to =" {
451+ name: 'code',
452+ params: { path: [...pkg.name.split('/'), 'v', displayVersion.version] },
453+ }"
436454 class =" link-subtle font-mono text-sm inline-flex items-center gap-1.5"
437455 >
438456 <span class =" i-carbon-code w-4 h-4" />
@@ -560,7 +578,7 @@ defineOgImageComponent('Package', {
560578 </h2 >
561579 <ul class =" flex flex-wrap gap-1.5 list-none m-0 p-0" >
562580 <li v-for =" keyword in displayVersion.keywords.slice(0, 15)" :key =" keyword" >
563- <NuxtLink :to =" `/ search?q= keywords:${encodeURIComponent( keyword)}` " class =" tag" >
581+ <NuxtLink :to =" { name: ' search', query: { q: ` keywords:${keyword}` } } " class =" tag" >
564582 {{ keyword }}
565583 </NuxtLink >
566584 </li >
0 commit comments