Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions app/components/OgImage/Package.d.vue.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/pages/about.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ useSeoMeta({
twitterDescription: () => $t('about.meta_description'),
})

defineOgImageComponent('Default', {
defineOgImage('Default', {
primaryColor: '#60a5fa',
title: 'About npmx',
description: 'a fast, modern browser for the **npm registry**',
Expand Down
2 changes: 1 addition & 1 deletion app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ useSeoMeta({
twitterDescription: () => $t('seo.home.description'),
})

defineOgImageComponent('Default', {
defineOgImage('Default', {
primaryColor: '#60a5fa',
title: 'npmx',
description: 'a fast, modern browser for the **npm registry**',
Expand Down
6 changes: 3 additions & 3 deletions app/pages/org/[org].vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ useSeoMeta({
twitterDescription: () => `npm packages published by the ${orgName.value} organization`,
})

defineOgImageComponent('Default', {
title: () => `@${orgName.value}`,
description: () => (packageCount.value ? `${packageCount.value} packages` : 'npm organization'),
defineOgImage('Default', {
title: `@${orgName.value}`,
description: packageCount.value ? `${packageCount.value} packages` : 'npm organization',
primaryColor: '#60a5fa',
})
Comment on lines +147 to 151
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

nuxt-og-image v6 defineOgImage reactivity

💡 Result:

In nuxt-og-image v6, defineOgImage() should be treated as non-reactive by default: it sets the OG meta once at the time you call it (during setup / SSR). So if you pass values that change later on the client, crawlers won’t see those updates anyway (they only read the initial HTML), and your tags won’t update unless you call defineOgImage() again. [1]

Recommended pattern (SSR-safe)

Fetch/compute the data before calling defineOgImage() (e.g. await useAsyncData(...), then call it with the resolved values). This is also how the docs show it for Content-driven pages. [2]

If you truly need “reactivity” (client-side navigation)

Re-run it when inputs change:

const route = useRoute()

watchEffect(() => {
  defineOgImage('NuxtSeo', {
    title: route.meta.title ?? 'Default',
  })
})

When the options/props change, nuxt-og-image generates a different OG image URL (and caches by those inputs), so re-calling it with new values is the intended way to “update” the OG image definition. [3]

Sources: defineOgImage API docs [1], Nuxt Content integration example [2], caching/invalidation behavior [3].

[1] https://nuxtseo.com/docs/og-image/api/define-og-image
[2] https://nuxtseo.com/docs/og-image/integrations/content
[3] https://nuxtseo.com/docs/og-image/guides/cache


🏁 Script executed:

cat -n app/pages/org/[org].vue | head -60

Repository: npmx-dev/npmx.dev

Length of output: 2030


🏁 Script executed:

sed -n '140,160p' app/pages/org/[org].vue

Repository: npmx-dev/npmx.dev

Length of output: 853


🏁 Script executed:

grep -n "useOrgPackages" app/pages/org/[org].vue

Repository: npmx-dev/npmx.dev

Length of output: 128


🏁 Script executed:

find . -name "*.ts" -o -name "*.js" -o -name "*.vue" | xargs grep -l "export.*useOrgPackages" | head -5

Repository: npmx-dev/npmx.dev

Length of output: 100


🏁 Script executed:

cat -n app/composables/npm/useOrgPackages.ts

Repository: npmx-dev/npmx.dev

Length of output: 6789


🏁 Script executed:

grep -n "emptySearchResponse" app/composables/npm/useNpmSearch.ts | head -5

Repository: npmx-dev/npmx.dev

Length of output: 259


🏁 Script executed:

sed -n '47,53p' app/composables/npm/useNpmSearch.ts

Repository: npmx-dev/npmx.dev

Length of output: 207


🏁 Script executed:

grep -n "defineOgImage\|watchEffect" app/pages/org/[org].vue

Repository: npmx-dev/npmx.dev

Length of output: 91


The OG image description will always show 'npm organisation' instead of the actual package count.

useOrgPackages uses useLazyAsyncData, which does not block page setup and defaults to returning an empty emptySearchResponse (with objects: []). When defineOgImage is called during setup, packageCount.value will be 0, causing the ternary to always resolve to the fallback text.

According to the nuxt-og-image v6 documentation, defineOgImage() is non-reactive and should be called only after data is available. To fix this, either await the data before calling defineOgImage, or use watchEffect to re-call it once the lazy data resolves:

watchEffect(() => {
  defineOgImage('Default', {
    title: `@${orgName.value}`,
    description: packageCount.value ? `${packageCount.value} packages` : 'npm organisation',
    primaryColor: '#60a5fa',
  })
})

</script>
Expand Down
6 changes: 3 additions & 3 deletions app/pages/package-code/[...path].vue
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,9 @@ useSeoMeta({
twitterDescription: () => `Browse source code for ${packageName.value}@${version.value}`,
})

defineOgImageComponent('Default', {
title: () => `${pkg.value?.name ?? 'Package'} - Code`,
description: () => pkg.value?.license ?? '',
defineOgImage('Default', {
title: `${pkg.value?.name ?? 'Package'} - Code`,
description: pkg.value?.license ?? '',
primaryColor: '#60a5fa',
})
</script>
Expand Down
6 changes: 3 additions & 3 deletions app/pages/package-docs/[...path].vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ useSeoMeta({
twitterDescription: () => pkg.value?.license ?? '',
})

defineOgImageComponent('Default', {
title: () => `${pkg.value?.name ?? 'Package'} - Docs`,
description: () => pkg.value?.license ?? '',
defineOgImage('Default', {
title: `${pkg.value?.name ?? 'Package'} - Docs`,
description: pkg.value?.license ?? '',
primaryColor: '#60a5fa',
})
Comment on lines +110 to 114
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same async-data concern as package-code/[...path].vue.

pkg.value is fetched asynchronously via usePackage, so title and description will resolve to their fallbacks ("Package - Docs" and "") if the data hasn't loaded when defineOgImage is evaluated. See the verification on the package-code file for the same pattern.


Expand Down
13 changes: 7 additions & 6 deletions app/pages/package/[[org]]/[name].vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ import { useModal } from '~/composables/useModal'
import { useAtproto } from '~/composables/atproto/useAtproto'
import { togglePackageLike } from '~/utils/atproto/likes'

defineOgImageComponent('Package', {
name: () => packageName.value,
version: () => requestedVersion.value ?? '',
primaryColor: '#60a5fa',
})

const router = useRouter()

const header = useTemplateRef('header')
Expand All @@ -48,6 +42,13 @@ onMounted(() => {
})

const { packageName, requestedVersion, orgName } = usePackageRoute()

defineOgImage('Package', {
name: packageName.value,
version: requestedVersion.value || '',
primaryColor: '#60a5fa',
})

const selectedPM = useSelectedPackageManager()
const activePmId = computed(() => selectedPM.value ?? 'npm')

Expand Down
6 changes: 3 additions & 3 deletions app/pages/privacy.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ useSeoMeta({
description: () => $t('privacy_policy.welcome', { app: 'npmx' }),
})

defineOgImageComponent('Default', {
title: () => $t('privacy_policy.title'),
description: () => $t('privacy_policy.welcome', { app: 'npmx' }),
defineOgImage('Default', {
title: $t('privacy_policy.title'),
description: $t('privacy_policy.welcome', { app: 'npmx' }),
})

const router = useRouter()
Expand Down
14 changes: 7 additions & 7 deletions app/pages/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -650,13 +650,13 @@ useSeoMeta({
: $t('search.meta_description_packages'),
})

defineOgImageComponent('Default', {
title: () =>
`${query.value ? $t('search.title_search', { search: query.value }) : $t('search.title_packages')} - npmx`,
description: () =>
query.value
? $t('search.meta_description', { search: query.value })
: $t('search.meta_description_packages'),
defineOgImage('Default', {
title: query.value
? `${$t('search.title_search', { search: query.value })} - npmx`
: `${$t('search.title_packages')} - npmx`,
description: query.value
? $t('search.meta_description', { search: query.value })
: $t('search.meta_description_packages'),
primaryColor: '#60a5fa',
})
</script>
Expand Down
6 changes: 3 additions & 3 deletions app/pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ useSeoMeta({
twitterDescription: () => $t('settings.meta_description'),
})

defineOgImageComponent('Default', {
title: () => $t('settings.title'),
description: () => $t('settings.tagline'),
defineOgImage('Default', {
title: $t('settings.title'),
description: $t('settings.tagline'),
primaryColor: '#60a5fa',
})

Expand Down
6 changes: 3 additions & 3 deletions app/pages/~[username]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,9 @@ useSeoMeta({
twitterDescription: () => `npm packages maintained by ${username.value}`,
})

defineOgImageComponent('Default', {
title: () => `~${username.value}`,
description: () => (results.value ? `${results.value.total} packages` : 'npm user profile'),
defineOgImage('Default', {
title: `~${username.value}`,
description: results.value ? `${results.value.total} packages` : 'npm user profile',
primaryColor: '#60a5fa',
})
Comment on lines +173 to 177
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: results.value is async, so OG description will likely always be 'npm user profile'.

results from useNpmSearch won't have resolved when defineOgImage is evaluated during SSR, so the ternary will always take the fallback branch. If showing the package count in the OG image was intentional, this is a regression from the previous function-wrapped approach. Otherwise, the fallback is perfectly sensible.

</script>
Expand Down
16 changes: 7 additions & 9 deletions app/pages/~[username]/orgs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,13 @@ useSeoMeta({
twitterDescription: () => `npm organizations for ${username.value}`,
})

defineOgImageComponent('Default', {
title: () => `@${username.value}`,
description: () => {
if (isLoading.value) return 'npm organizations'
if (orgs.value.length === 0) return 'No organizations found'

const count = orgs.value.length
return `${count} ${count === 1 ? 'organization' : 'organizations'}`
},
defineOgImage('Default', {
title: `@${username.value}`,
description: isLoading.value
? 'npm organizations'
: orgs.value.length === 0
? 'No organizations found'
: `${orgs.value.length} ${orgs.value.length === 1 ? 'organization' : 'organizations'}`,
primaryColor: '#60a5fa',
})
</script>
Expand Down
3 changes: 2 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export default defineNuxtConfig({
'/api/registry/files/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
'/:pkg/.well-known/skills/**': { isr: 3600 },
'/:scope/:pkg/.well-known/skills/**': { isr: 3600 },
'/__og-image__/**': { isr: getISRConfig(60) },
'/_og/**': { isr: getISRConfig(60) },
'/_avatar/**': { isr: 3600, proxy: 'https://www.gravatar.com/avatar/**' },
'/opensearch.xml': { isr: true },
'/oauth-client-metadata.json': { prerender: true },
Expand Down Expand Up @@ -204,6 +204,7 @@ export default defineNuxtConfig({
ogImage: {
defaults: {
component: 'Default',
renderer: 'takumi',
},
},

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"npmx-connector": "pnpm --filter npmx-connector dev",
"generate-pwa-icons": "pwa-assets-generator",
"preview": "nuxt preview",
"postinstall": "pnpm rebuild @resvg/resvg-js && pnpm generate:lexicons && nuxt prepare && simple-git-hooks",
"postinstall": "pnpm generate:lexicons && nuxt prepare && simple-git-hooks",
"generate:fixtures": "node scripts/generate-fixtures.ts",
"generate:lexicons": "lex build --lexicons lexicons --out shared/types/lexicons --clear",
"test": "vite test",
Expand Down Expand Up @@ -64,6 +64,7 @@
"@nuxtjs/i18n": "10.2.1",
"@shikijs/langs": "3.21.0",
"@shikijs/themes": "3.21.0",
"@takumi-rs/core": "0.67.0",
"@unocss/nuxt": "66.6.0",
"@unocss/preset-wind4": "66.6.0",
"@upstash/redis": "1.36.1",
Expand All @@ -81,7 +82,7 @@
"marked": "17.0.1",
"module-replacements": "2.11.0",
"nuxt": "4.3.0",
"nuxt-og-image": "5.1.13",
"nuxt-og-image": "6.0.0-beta.15",
"ofetch": "1.5.1",
"perfect-debounce": "2.1.0",
"sanitize-html": "2.17.0",
Expand Down
Loading
Loading