Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0de13bc
chore: dump all dependency versions to latest
toddeTV May 18, 2026
764bf6d
chore: dump all dependency versions to latest
toddeTV May 18, 2026
b0ee2d9
docs: document deployment and OG image secrets in env example
toddeTV May 18, 2026
8f2d27c
fix: prebundle schema.org and vue devtools dependencies
toddeTV May 18, 2026
33c1a09
feat: add favicon and app icon assets due to migration after nuxt-og-…
toddeTV May 18, 2026
cc8289c
chore: expand gitignore for tool outputs and local files
toddeTV May 18, 2026
f7a6364
Merge branch 'main' into chore/update-all-dependencies-to-latest
toddeTV May 18, 2026
ce93079
chore: configure pnpm workspace catalog and install policies
toddeTV May 18, 2026
1377c5a
chore: add vite-plus workspace catalog dependency
toddeTV May 18, 2026
fb46825
fix: pin nuxt-seo-utils and allow vite-plus peer versions
toddeTV May 18, 2026
db24caa
chore: regenerate pnpm lock file
toddeTV May 18, 2026
423336d
refactor: replace deprecated seo content wrapper
toddeTV May 18, 2026
05be1bf
refactor: migrate og image v6 satori setup
toddeTV May 18, 2026
0e09b28
fix: support nuxt 4.4.6 static build
toddeTV May 18, 2026
4d0c13a
docs: add comment to temp fix
toddeTV May 18, 2026
d866578
fix: no file name suffixes for components used in satori templates
toddeTV May 18, 2026
2d0c16b
fix: correct hyperlink paths for sitemap lint
toddeTV May 18, 2026
3082c67
refactor: move og image components out of og image template folder
toddeTV May 18, 2026
5293ef6
docs: document eslint disable rules
toddeTV May 18, 2026
2411f2d
refactor: remove not needed quotes from example env file
toddeTV May 18, 2026
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
27 changes: 15 additions & 12 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# Environment Variables
#
# Copy this file to `.env` and fill in the values for local development.
# The `.env` file is gitignored and will not be committed.
#
# In production (GitHub Actions), these values are set via GitHub Variables
# (Settings > Secrets and variables > Actions > Variables).
# In production, only set in GitHub Actions. (Settings -> Secrets and variables -> Actions -> Secrets/Variables)

# Cloudflare API credentials for deploying.
# -> Secrets
CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_API_TOKEN=

# Legal page data (address and VAT ID for Impressum / Legal Notice).
# These are injected into `runtimeConfig.public` at build time via Nuxt's `NUXT_PUBLIC_*` env var convention.
NUXT_PUBLIC_LEGAL_ADDRESS_STREET="Musterstrasse 1"
NUXT_PUBLIC_LEGAL_ADDRESS_CITY="01234 Musterstadt"
NUXT_PUBLIC_LEGAL_ADDRESS_COUNTRY="Germany"
NUXT_PUBLIC_LEGAL_VAT_ID="DE123456789"
# -> Variables
NUXT_PUBLIC_LEGAL_ADDRESS_STREET=Musterstrasse 1
NUXT_PUBLIC_LEGAL_ADDRESS_CITY=01234 Musterstadt
NUXT_PUBLIC_LEGAL_ADDRESS_COUNTRY=Germany
Comment thread
toddeTV marked this conversation as resolved.
NUXT_PUBLIC_LEGAL_VAT_ID=DE123456789

# Nuxt OG Image Generation Secret (used for signing OG image URLs).
# -> Secrets
NUXT_OG_IMAGE_SECRET=
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,31 @@
.nitro
.cache
dist
.vite-hooks

# Node dependencies
node_modules

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Misc
.DS_Store
.fleet
.fleetrm
dist-ssr
*.local
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Local env files
.env
Expand Down
9 changes: 9 additions & 0 deletions app/components/layout/SiteFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,24 @@ const yearSpan = computed(() =>
</p>

<p class="text-center text-xs text-text-dim">
<!-- `link-checker/valid-sitemap-link` does not resolve content-backed catch-all pages here, so we need: -->
<!-- eslint-disable-next-line link-checker/valid-sitemap-link -->
<NuxtLink class="text-xs text-text-dim hover:text-text" to="/legal-notice">
Legal Notice
</NuxtLink>

<span class="mx-1.5">·</span>

<!-- `link-checker/valid-sitemap-link` does not resolve content-backed catch-all pages here, so we need: -->
<!-- eslint-disable-next-line link-checker/valid-sitemap-link -->
<NuxtLink class="text-xs text-text-dim hover:text-text" to="/privacy-policy">
Privacy Policy
</NuxtLink>

<span class="mx-1.5">·</span>

Source on

<NuxtLink
class="text-xs"
target="_blank"
Expand Down
20 changes: 15 additions & 5 deletions app/pages/[...slug].vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { OgImageComponents } from '#og-image/components'

const route = useRoute()

// Strip trailing slash to prevent hydration key mismatches on CDNs that redirect
Expand All @@ -16,14 +18,22 @@ if (!page.value) {
if (page.value.seo) {
useSeoMeta(page.value.seo)
}
if (page.value.head) {
useHead(page.value.head as Record<string, unknown>)
if (page.value.schemaOrg) {
useSchemaOrg(page.value.schemaOrg)
}
if (page.value.ogImage?.url) {
useSeoMeta({
ogImage: page.value.ogImage.url,
})
}
if (page.value.ogImage) {
defineOgImage(page.value.ogImage)
else if (page.value.ogImage?.component) {
defineOgImage(
page.value.ogImage.component as keyof OgImageComponents,
page.value.ogImage.props,
)
}
else {
defineOgImageComponent('Default')
defineOgImage('Default')
}
</script>

Expand Down
2 changes: 1 addition & 1 deletion app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ useSeoMeta({
+ 'Nuxt, 3D on the web, and full-stack development.',
})

defineOgImageComponent('Home', {
defineOgImage('Home', {
title: 'Thorsten Seyschab',
description: 'IT consultant, senior full-stack developer, and conference speaker.',
})
Expand Down
2 changes: 1 addition & 1 deletion app/pages/projects/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ useSeoMeta({
description: project.value.description,
})

defineOgImageComponent('Project', {
defineOgImage('Project', {
title: project.value.name,
description: project.value.description,
repoStars: project.value.repoStars,
Expand Down
2 changes: 1 addition & 1 deletion app/pages/projects/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ useSeoMeta({
description: 'Projects by Thorsten Seyschab (toddeTV).',
})

defineOgImageComponent('Project', {
defineOgImage('Project', {
title: 'Projects',
description: 'Tools, experiments, and applications for 3D on the web, Vue/Nuxt, and developer tooling.',
})
Expand Down
2 changes: 1 addition & 1 deletion app/pages/talks/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ useSeoMeta({
description: talk.value.description || `${talk.value.title} at ${talk.value.event} in ${talk.value.location}.`,
})

defineOgImageComponent('Talk', {
defineOgImage('Talk', {
title: talk.value.title,
event: talk.value.event,
date: talk.value.date,
Expand Down
2 changes: 1 addition & 1 deletion app/pages/talks/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ useSeoMeta({
+ 'tech and life topics - from Vue.js, Nuxt, and Vite plugins to 3D on the web.',
})

defineOgImageComponent('Talk', {
defineOgImage('Talk', {
title: 'Talks',
event: 'Conference talks & speaking engagements',
})
Expand Down
2 changes: 1 addition & 1 deletion app/pages/vcard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ useSeoMeta({
// Personal utility page - exclude from search engine indexing (robots file)
useRobotsRule(false)

defineOgImageComponent('Default')
defineOgImage('Default')

const { data: socials } = await useAsyncData('vcard-socials', () =>
queryCollection('socials').where('active', '=', true).order('sortOrder', 'ASC').all(),
Expand Down
25 changes: 16 additions & 9 deletions content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
*/

import { defineContentConfig, defineCollection } from '@nuxt/content'
import { asSeoCollection } from '@nuxtjs/seo/content'
import { defineRobotsSchema } from '@nuxtjs/robots/content'
import { defineSitemapSchema } from '@nuxtjs/sitemap/content'
import { defineSchemaOrgSchema } from 'nuxt-schema-org/content'
import { defineOgImageSchema } from 'nuxt-og-image/content'
import { z } from 'zod'

const testimonialSchema = z.object({
Expand All @@ -18,15 +21,19 @@ const testimonialSchema = z.object({

export default defineContentConfig({
collections: {
content: defineCollection(
asSeoCollection({
type: 'page',
source: {
include: 'pages/**',
prefix: '/',
},
content: defineCollection({
type: 'page',
source: {
include: 'pages/**',
prefix: '/',
},
schema: z.object({
robots: defineRobotsSchema(),
sitemap: defineSitemapSchema(),
ogImage: defineOgImageSchema(),
schemaOrg: defineSchemaOrgSchema(),
}),
),
}),

socials: defineCollection({
type: 'data',
Expand Down
10 changes: 7 additions & 3 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ export default defineNuxtConfig({
},

vite: {
optimizeDeps: {
include: [
'@unhead/schema-org/vue',
'@vue/devtools-core',
'@vue/devtools-kit',
],
},
plugins: [
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tailwindcss() as any,
Expand Down Expand Up @@ -245,9 +252,6 @@ export default defineNuxtConfig({
'OgImage',
// 'OgImageTemplate',
],
defaults: {
component: 'Default',
},
},

robots: { // for `nuxt-robots` (via `@nuxtjs/seo`)
Expand Down
38 changes: 21 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,37 +62,41 @@
"test:types": "nuxt typecheck"
},
"dependencies": {
"nuxt": "~4.3.1",
"vue": "~3.5.29"
"nuxt": "~4.4.6",
"vue": "~3.5.34"
},
"devDependencies": {
"@iconify-json/noto": "~1.2.7",
"@iconify-json/ph": "~1.2.2",
"@iconify-json/simple-icons": "~1.2.72",
"@nuxt/content": "~3.12.0",
"@iconify-json/simple-icons": "~1.2.82",
"@nuxt/content": "~3.13.0",
"@nuxt/eslint": "~1.15.2",
"@nuxt/fonts": "~0.14.0",
"@nuxt/icon": "~2.2.1",
"@nuxt/icon": "~2.2.2",
"@nuxt/image": "~2.0.0",
"@nuxtjs/seo": "~3.4.0",
"@tailwindcss/vite": "~4.2.1",
"@nuxtjs/seo": "~5.1.3",
"@resvg/resvg-js": "~2.6.2",
"@tailwindcss/vite": "~4.3.0",
"@types/qrcode": "~1.5.6",
"better-sqlite3": "~12.6.2",
"better-sqlite3": "~12.10.0",
"cross-env": "~10.1.0",
"eslint": "~10.0.2",
"eslint": "~10.4.0",
"eslint-plugin-format": "~2.0.1",
"eslint-plugin-jsonc": "~3.1.1",
"eslint-plugin-jsonc": "~3.1.2",
"eslint-plugin-tailwindcss": "4.0.0-beta.0",
"eslint-plugin-yml": "~3.3.1",
"eslint-plugin-yml": "~3.3.2",
"npm-run-all2": "~8.0.4",
"nuxi": "~3.33.1",
"prettier": "~3.8.1",
"nuxi": "~3.35.2",
"prettier": "~3.8.3",
"qrcode": "~1.5.4",
"rimraf": "~6.1.3",
"rolldown": "~1.0.1",
"satori": "~0.26.0",
"serve": "~14.2.6",
"tailwindcss": "~4.2.1",
"typescript": "~5.9.3",
"vue-tsc": "~3.2.5",
"zod": "~4.3.6"
"tailwindcss": "~4.3.0",
"typescript": "~6.0.3",
"vite-plus": "catalog:",
"vue-tsc": "~3.2.9",
"zod": "~4.4.3"
}
}
18 changes: 18 additions & 0 deletions patches/nuxt-og-image@6.5.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
diff --git a/dist/runtime/server/util/kit.js b/dist/runtime/server/util/kit.js
index 386f4cc7beb21f2402ce1a33846d1314c1950d1f..95e6acee61a440a44c947c0726a0d73894373527 100644
--- a/dist/runtime/server/util/kit.js
+++ b/dist/runtime/server/util/kit.js
@@ -1,11 +1,11 @@
import { defu } from "defu";
+import { computeIslandHash, filterIslandProps } from "#app/island-hash";
import { useRuntimeConfig } from "nitropack/runtime";
-import { hash } from "ohash";
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
import { withoutBase, withoutTrailingSlash } from "ufo";
export { withoutQuery } from "nuxtseo-shared/utils";
export function fetchIsland(e, component, props, timeout) {
- const hashId = hash([component, props]).replaceAll("_", "-");
+ const hashId = computeIslandHash(component, filterIslandProps(props), {}, void 0);
const signal = timeout ? AbortSignal.timeout(timeout) : void 0;
return e.$fetch(`/__nuxt_island/${component}_${hashId}.json`, {
params: {
Loading
Loading