Update SEO and meta tags in index.html, enhance robots.txt for better crawling control, and improve sitemap.xml priorities. Refactor blog routes to include SEO metadata and adjust blog view for canonical URLs. Implement blog URL generation in BlogListView and apply SEO dynamically in BlogView.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
<div v-if="!blogs.length">Keine Blogs gefunden.</div>
|
||||
<ul>
|
||||
<li v-for="b in blogs" :key="b.id">
|
||||
<router-link :to="`/blogs/${b.id}`">{{ b.title }}</router-link>
|
||||
<router-link :to="blogUrl(b)">{{ b.title }}</router-link>
|
||||
<small> – {{ b.owner?.username }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -19,11 +19,18 @@
|
||||
|
||||
<script>
|
||||
import { listBlogs } from '@/api/blogApi.js';
|
||||
import { createBlogSlug } from '@/utils/seo.js';
|
||||
export default {
|
||||
name: 'BlogListView',
|
||||
data: () => ({ blogs: [], loading: true }),
|
||||
async mounted() {
|
||||
try { this.blogs = await listBlogs(); } finally { this.loading = false; }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
blogUrl(blog) {
|
||||
const slug = createBlogSlug(blog?.owner?.username, blog?.title);
|
||||
return slug ? `/blogs/${encodeURIComponent(slug)}` : `/blogs/${blog.id}`;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import { getBlog, listPosts, createPost } from '@/api/blogApi.js';
|
||||
import DOMPurify from 'dompurify';
|
||||
import RichTextEditor from './components/RichTextEditor.vue';
|
||||
import { applySeo, buildAbsoluteUrl, createBlogSlug, stripHtml, truncateText } from '@/utils/seo.js';
|
||||
export default {
|
||||
name: 'BlogView',
|
||||
props: { id: String, slug: String },
|
||||
@@ -46,9 +47,73 @@ export default {
|
||||
isOwner() {
|
||||
const u = this.$store.getters.user;
|
||||
return !!(u && this.blog && this.blog.owner && this.blog.owner.hashedId === u.id);
|
||||
}
|
||||
},
|
||||
pages() {
|
||||
return Math.max(1, Math.ceil(this.total / this.pageSize));
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadBlog();
|
||||
},
|
||||
watch: {
|
||||
'$route.fullPath': {
|
||||
async handler() {
|
||||
await this.loadBlog();
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
canonicalBlogPath() {
|
||||
const slug = createBlogSlug(this.blog?.owner?.username, this.blog?.title);
|
||||
if (slug) {
|
||||
return `/blogs/${encodeURIComponent(slug)}`;
|
||||
}
|
||||
|
||||
return this.blog?.id ? `/blogs/${this.blog.id}` : '/blogs';
|
||||
},
|
||||
applyBlogSeo() {
|
||||
if (!this.blog) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plainTextPosts = this.items
|
||||
.map((item) => `${item.title || ''} ${stripHtml(item.content || '')}`.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
const summarySource = this.blog.description || plainTextPosts || 'Oeffentlicher Community-Blog auf YourPart.';
|
||||
const description = truncateText(summarySource, 160);
|
||||
const canonicalPath = this.canonicalBlogPath();
|
||||
|
||||
applySeo({
|
||||
title: `${this.blog.title} | Blog auf YourPart`,
|
||||
description,
|
||||
canonicalPath,
|
||||
keywords: `Blog, ${this.blog.title}, ${this.blog.owner?.username || 'YourPart'}, Community`,
|
||||
type: 'article',
|
||||
jsonLd: [
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Blog',
|
||||
name: this.blog.title,
|
||||
description,
|
||||
url: buildAbsoluteUrl(canonicalPath),
|
||||
inLanguage: 'de',
|
||||
author: this.blog.owner?.username
|
||||
? {
|
||||
'@type': 'Person',
|
||||
name: this.blog.owner.username,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
async loadBlog() {
|
||||
this.loading = true;
|
||||
this.blog = null;
|
||||
this.items = [];
|
||||
this.resolvedId = null;
|
||||
|
||||
try {
|
||||
let id = this.$route.params.id;
|
||||
// If we have a slug route param or the id is non-numeric, resolve to id
|
||||
@@ -67,12 +132,18 @@ export default {
|
||||
const useId = id || this.resolvedId;
|
||||
this.blog = await getBlog(useId);
|
||||
await this.fetchPage(1);
|
||||
this.applyBlogSeo();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
// this.$router.replace('/blogs');
|
||||
applySeo({
|
||||
title: 'Blog nicht gefunden | YourPart',
|
||||
description: 'Der angeforderte Blog konnte nicht geladen werden.',
|
||||
canonicalPath: '/blogs',
|
||||
robots: 'noindex, nofollow',
|
||||
});
|
||||
} finally { this.loading = false; }
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
sanitize(html) {
|
||||
return DOMPurify.sanitize(html || '');
|
||||
},
|
||||
@@ -83,8 +154,8 @@ export default {
|
||||
this.page = res.page;
|
||||
this.pageSize = res.pageSize;
|
||||
this.total = res.total;
|
||||
this.applyBlogSeo();
|
||||
},
|
||||
get pages() { return Math.max(1, Math.ceil(this.total / this.pageSize)); },
|
||||
async go(p) { if (p>=1 && p<=this.pages) await this.fetchPage(p); },
|
||||
async addPost() {
|
||||
if (!this.newPost.title || !this.newPost.content) return;
|
||||
@@ -106,4 +177,4 @@ export default {
|
||||
padding: 0.2em 0.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user