feat: Initial SEO integration

This commit is contained in:
2026-01-26 11:57:35 -05:00
parent 9cd99c36db
commit c5ce607fae
8 changed files with 2464 additions and 26 deletions

View File

@@ -0,0 +1,20 @@
import type { NodeByUriQueryResult, NodeSeoFragment } from "#graphql/operations";
export function useNodeSeo(node: NodeByUriQueryResult["nodeByUri"]) {
// Check if node has SEO data
if (!node || !("seo" in node) || !node.seo) {
return;
}
const { seo } = node as NodeSeoFragment;
useSeoMeta({
title: seo?.title || undefined,
description: seo?.description || undefined,
robots: (seo?.robots || []).join(", "),
ogTitle: seo?.openGraph?.title || undefined,
ogDescription: seo?.openGraph?.description || undefined,
ogImage: seo?.openGraph?.image?.url || undefined,
ogUrl: seo?.canonicalUrl || undefined,
twitterCard: "summary_large_image",
});
}

View File

@@ -1,8 +1,27 @@
fragment NodeSeo on NodeWithRankMathSeo {
seo {
title
description
robots
canonicalUrl
openGraph {
title
description
image {
url
}
}
}
}
query NodeByUri($uri: String!) {
nodeByUri(uri: $uri) {
__typename
... on Page {
... NodePage
}
... on NodeWithRankMathSeo {
... NodeSeo
}
}
}

View File

@@ -1,8 +1,7 @@
<script setup lang="ts">
// Resolve Node component from URI
const { path: uri } = useRoute();
const { data } = await useAsyncGraphQLQuery("NodeByUri", { uri });
// Resolve and validate Node component
if (!data.value.nodeByUri) {
throw createError({ statusCode: 404, message: `La page demandée est introuvable: ${uri}`, fatal: true });
}
@@ -10,10 +9,13 @@ const componentName = `Node${data.value.nodeByUri.__typename}`;
if (!useNuxtApp().vueApp.component(componentName)) {
throw createError({ statusCode: 404, message: `La page demandée ne peut pas être affichée correctement: ${componentName}`, fatal: true });
}
useNodeSeo(data.value.nodeByUri);
</script>
<template>
<div v-if="data.nodeByUri" id="page-node-from-uri">
<Component :is="componentName" v-bind="data.nodeByUri" />
<pre>{{ data.nodeByUri }}</pre>
</div>
</template>