Back to Dynamic Webapps
Vue.js Integration
Vue.js Integration Guide
Integrate SEO Sniper articles into your Vue 3 application using Composition API. Includes Nuxt.js examples for SSR.
Articles Composable
typescript
// composables/useArticles.ts
import { ref, computed } from 'vue';
export interface Article {
id: string;
slug: string;
title: string;
metaTitle: string;
metaDescription: string;
contentHtml: string;
publishedAt: string;
featuredImageUrl?: string;
}
const API_URL = import.meta.env.VITE_SEOSNIPER_API_URL;
const API_TOKEN = import.meta.env.VITE_SEOSNIPER_API_TOKEN;
const articles = ref<Article[]>([]);
const loading = ref(false);
const error = ref<Error | null>(null);
export function useArticles() {
const fetchArticles = async () => {
if (articles.value.length > 0) return; // Use cache
loading.value = true;
error.value = null;
try {
const response = await fetch(
`${API_URL}/api/v1/articles`,
{
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
}
);
if (!response.ok) {
throw new Error('Failed to fetch articles');
}
const data = await response.json();
articles.value = data.articles;
} catch (err) {
error.value = err as Error;
} finally {
loading.value = false;
}
};
const getArticleBySlug = (slug: string) => {
return computed(() =>
articles.value.find((a) => a.slug === slug)
);
};
return {
articles,
loading,
error,
fetchArticles,
getArticleBySlug
};
}Blog List Component
vue
<!-- views/BlogList.vue -->
<script setup lang="ts">
import { onMounted } from 'vue';
import { RouterLink } from 'vue-router';
import { useArticles } from '@/composables/useArticles';
const { articles, loading, error, fetchArticles } = useArticles();
onMounted(() => {
fetchArticles();
});
</script>
<template>
<div class="max-w-4xl mx-auto py-12 px-4">
<h1 class="text-4xl font-bold mb-8">Blog</h1>
<div v-if="loading" class="text-center py-8">
Loading articles...
</div>
<div v-else-if="error" class="text-red-500">
Error: {{ error.message }}
</div>
<div v-else class="grid gap-8">
<article
v-for="article in articles"
:key="article.id"
class="border rounded-lg overflow-hidden"
>
<img
v-if="article.featuredImageUrl"
:src="article.featuredImageUrl"
:alt="article.title"
class="w-full h-48 object-cover"
/>
<div class="p-6">
<RouterLink :to="`/blog/${article.slug}`">
<h2 class="text-2xl font-semibold hover:text-blue-600">
{{ article.title }}
</h2>
</RouterLink>
<p class="text-gray-600 mt-2">
{{ article.metaDescription }}
</p>
<time class="text-sm text-gray-500 mt-4 block">
{{ new Date(article.publishedAt).toLocaleDateString() }}
</time>
</div>
</article>
</div>
</div>
</template>Article Page Component
vue
<!-- views/ArticlePage.vue -->
<script setup lang="ts">
import { onMounted, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useHead } from '@vueuse/head';
import { useArticles } from '@/composables/useArticles';
const route = useRoute();
const { fetchArticles, getArticleBySlug } = useArticles();
const slug = route.params.slug as string;
const article = getArticleBySlug(slug);
onMounted(() => {
fetchArticles();
});
// Update meta tags when article loads
watch(article, (newArticle) => {
if (newArticle) {
useHead({
title: newArticle.metaTitle,
meta: [
{ name: 'description', content: newArticle.metaDescription },
{ property: 'og:title', content: newArticle.metaTitle },
{ property: 'og:description', content: newArticle.metaDescription },
...(newArticle.featuredImageUrl ? [
{ property: 'og:image', content: newArticle.featuredImageUrl }
] : [])
]
});
}
});
</script>
<template>
<article v-if="article" class="max-w-3xl mx-auto py-12 px-4">
<header class="mb-8">
<h1 class="text-4xl font-bold mb-4">{{ article.title }}</h1>
<time class="text-gray-500">
{{ new Date(article.publishedAt).toLocaleDateString() }}
</time>
</header>
<!-- Render HTML content -->
<div
class="prose prose-lg max-w-none"
v-html="article.contentHtml"
/>
</article>
<div v-else class="text-center py-12">
Article not found
</div>
</template>Vue Router Configuration
typescript
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import BlogList from '@/views/BlogList.vue';
import ArticlePage from '@/views/ArticlePage.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/blog', name: 'blog', component: BlogList },
{ path: '/blog/:slug', name: 'article', component: ArticlePage }
]
});
export default router;