Skip to content

Commit 79b2730

Browse files
committed
feat: implement SEO enhancements with Schema.org markup and sitemap generation
1 parent 898fab9 commit 79b2730

4 files changed

Lines changed: 67 additions & 3 deletions

File tree

app/pages/posts/[slug].vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ useSeoMeta({
2222
ogDescription: description
2323
})
2424
25+
// Schema.org Article markup for rich snippets
26+
useSchemaOrg([
27+
defineArticle({
28+
'@type': 'BlogPosting',
29+
headline: title,
30+
description,
31+
datePublished: post.value.date,
32+
image: post.value.image?.src,
33+
author: {
34+
'@type': 'Person',
35+
name: 'Alexandre Nédélec',
36+
url: 'https://techwatching.dev/about'
37+
}
38+
})
39+
])
40+
2541
if (post.value.image?.src) {
2642
defineOgImage({
2743
url: post.value.image.src

nuxt.config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default defineNuxtConfig({
66
'@nuxt/ui',
77
'@nuxt/content',
88
'@vueuse/nuxt',
9-
'nuxt-og-image',
9+
'@nuxtjs/seo',
1010
'@nuxt/hints',
1111
'@nuxt/scripts',
1212
'@stefanobartoletti/nuxt-social-share'
@@ -26,13 +26,19 @@ export default defineNuxtConfig({
2626

2727
site: {
2828
url: 'https://techwatching.dev',
29-
name: 'Alexandre Nédélec'
29+
name: "Alexandre Nédélec's personal website",
30+
description: "Alexandre Nédélec's personal website"
3031
},
3132

3233
socialShare: {
3334
baseUrl: 'https://techwatching.dev'
3435
},
3536

37+
sitemap: {
38+
sources: ['/api/__sitemap__/urls'],
39+
exclude: ['/login', '/signup']
40+
},
41+
3642
runtimeConfig: {
3743
public: {
3844
submitJsonApiKey: ''

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
"@nuxt/image": "^2.0.0",
2020
"@nuxt/scripts": "0.13.2",
2121
"@nuxt/ui": "^4.2.1",
22+
"@nuxtjs/seo": "^3.3.0",
2223
"@standard-schema/spec": "^1.0.0",
2324
"@unhead/vue": "^2.0.19",
2425
"@vueuse/nuxt": "^13.9.0",
2526
"better-sqlite3": "^12.4.6",
2627
"just-kebab-case": "^4.2.0",
2728
"nuxt": "^4.2.2",
28-
"nuxt-og-image": "^5.1.12",
2929
"posthog-js": "^1.310.1",
3030
"submitjson": "^0.13.0",
3131
"zod": "^4.1.13"

server/api/__sitemap__/urls.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { asSitemapUrl, defineSitemapEventHandler } from '#imports'
2+
import kebabCase from 'just-kebab-case'
3+
import type { Collections } from '@nuxt/content'
4+
5+
const getTagSlug = (tag: string) => kebabCase(tag.toLowerCase().replace('/', ''))
6+
const getTagRoute = (tag: string) => `/tags/${getTagSlug(tag)}`
7+
8+
export default defineSitemapEventHandler(async (e) => {
9+
// Query all posts
10+
const posts = await queryCollection(e, 'posts').all()
11+
12+
// Query all goodies
13+
const goodies = await queryCollection(e, 'goodies').all()
14+
15+
// Collect unique tags from posts
16+
const tags = new Set<string>()
17+
posts.forEach((post: Collections['posts']) => {
18+
post.tags?.forEach((tag: string) => {
19+
tags.add(getTagRoute(tag))
20+
})
21+
})
22+
23+
// Generate tag URLs
24+
const tagItems = [...tags].map((t) => asSitemapUrl({ loc: t }))
25+
26+
// Generate post URLs
27+
const postItems = posts.map((post: Collections['posts']) => {
28+
return asSitemapUrl({
29+
loc: post.path,
30+
lastmod: post.date ? new Date(post.date).toISOString() : undefined
31+
})
32+
})
33+
34+
// Generate goodie URLs
35+
const goodieItems = goodies.map((goodie: Collections['goodies']) => {
36+
return asSitemapUrl({
37+
loc: goodie.path
38+
})
39+
})
40+
41+
return [...tagItems, ...postItems, ...goodieItems]
42+
})

0 commit comments

Comments
 (0)