refactor: update schema handling and improve SEO metadata
- Changed schema type from WithContext<Thing> to Thing[] in Head, BaseLayout, and JsonLd components for better flexibility. - Added optional robots meta tag to Head component. - Updated JSON-LD generation in JsonLd component to include a structured payload. - Enhanced page and blog schemas to support breadcrumb and person schemas for improved SEO. - Introduced new utility schemas for website and person to streamline schema generation. - Refactored existing schemas to align with the new structure and ensure consistency across components.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { WithContext, BlogPosting } from "schema-dts";
|
||||
import { config } from "../../config";
|
||||
import type { BlogPosting } from "schema-dts";
|
||||
import { personId, websiteId } from "./ids";
|
||||
|
||||
export type BlogPostSchemaParams = {
|
||||
readonly dateModified: string;
|
||||
@@ -13,25 +13,26 @@ export type BlogPostSchemaParams = {
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
export default ({ siteUrl, slug, title, description, preview, datePublished, dateModified, lang, isBasedOn }: BlogPostSchemaParams): WithContext<BlogPosting> => ({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"url": new URL(`/blog/${slug}`, siteUrl).toString(),
|
||||
"headline": title,
|
||||
"description": description,
|
||||
"image": new URL(preview, siteUrl).toString(),
|
||||
"datePublished": datePublished,
|
||||
"dateModified": dateModified,
|
||||
"inLanguage": lang,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": config.author.name,
|
||||
"url": config.author.url,
|
||||
"sameAs": config.author.sameAs,
|
||||
},
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": new URL(`/blog/${slug}`, siteUrl).toString(),
|
||||
},
|
||||
...(isBasedOn && { isBasedOn: isBasedOn }),
|
||||
});
|
||||
export default ({ siteUrl, slug, title, description, preview, datePublished, dateModified, lang, isBasedOn }: BlogPostSchemaParams): BlogPosting => {
|
||||
const url = new URL(`/blog/${slug}`, siteUrl).toString();
|
||||
|
||||
return {
|
||||
"@type": "BlogPosting",
|
||||
"@id": url,
|
||||
"url": url,
|
||||
"headline": title,
|
||||
"description": description,
|
||||
"image": new URL(preview, siteUrl).toString(),
|
||||
"datePublished": datePublished,
|
||||
"dateModified": dateModified,
|
||||
"inLanguage": lang,
|
||||
"author": { "@id": personId(siteUrl) },
|
||||
"publisher": { "@id": personId(siteUrl) },
|
||||
"isPartOf": { "@id": websiteId(siteUrl) },
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": url,
|
||||
},
|
||||
...(isBasedOn && { isBasedOn: isBasedOn }),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
import type { WithContext, CollectionPage } from "schema-dts";
|
||||
import type { CollectionPage } from "schema-dts";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import { websiteId } from "./ids";
|
||||
|
||||
export type BlogSchemaParams = {
|
||||
readonly description: string;
|
||||
readonly lang: string;
|
||||
readonly posts: CollectionEntry<"blog">[];
|
||||
readonly siteUrl: string;
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
export default ({ siteUrl, title, posts }: BlogSchemaParams): WithContext<CollectionPage> => ({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
"url": new URL("/blog/", siteUrl).toString(),
|
||||
"name": title,
|
||||
"mainEntity": {
|
||||
"@type": "ItemList",
|
||||
"itemListOrder": "https://schema.org/ItemListOrderDescending",
|
||||
"numberOfItems": posts.length,
|
||||
"itemListElement": posts.map((post, index) => ({
|
||||
"@type": "ListItem",
|
||||
"position": index + 1,
|
||||
"url": new URL(`/blog/${post.id}`, siteUrl).toString(),
|
||||
"name": post.data.title,
|
||||
})),
|
||||
},
|
||||
});
|
||||
export default ({ siteUrl, title, description, lang, posts }: BlogSchemaParams): CollectionPage => {
|
||||
const url = new URL("/blog/", siteUrl).toString();
|
||||
|
||||
return {
|
||||
"@type": "CollectionPage",
|
||||
"@id": url,
|
||||
"url": url,
|
||||
"name": title,
|
||||
"description": description,
|
||||
"inLanguage": lang,
|
||||
"isPartOf": { "@id": websiteId(siteUrl) },
|
||||
"mainEntity": {
|
||||
"@type": "ItemList",
|
||||
"itemListOrder": "https://schema.org/ItemListOrderDescending",
|
||||
"numberOfItems": posts.length,
|
||||
"itemListElement": posts.map((post, index) => ({
|
||||
"@type": "ListItem",
|
||||
"position": index + 1,
|
||||
"url": new URL(`/blog/${post.id}`, siteUrl).toString(),
|
||||
"name": post.data.title,
|
||||
})),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { BreadcrumbList } from "schema-dts";
|
||||
|
||||
export type BreadcrumbItem = {
|
||||
readonly name: string;
|
||||
readonly url: string;
|
||||
};
|
||||
|
||||
export type BreadcrumbSchemaParams = {
|
||||
readonly items: BreadcrumbItem[];
|
||||
readonly siteUrl: string;
|
||||
};
|
||||
|
||||
export default ({ items, siteUrl }: BreadcrumbSchemaParams): BreadcrumbList => ({
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": items.map((item, index) => ({
|
||||
"@type": "ListItem",
|
||||
"position": index + 1,
|
||||
"name": item.name,
|
||||
"item": new URL(item.url, siteUrl).toString(),
|
||||
})),
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export const websiteId = (siteUrl: string): string => new URL("#website", siteUrl).toString();
|
||||
|
||||
export const personId = (siteUrl: string): string => new URL("#person", siteUrl).toString();
|
||||
@@ -1,23 +1,36 @@
|
||||
import type { WithContext, WebPage } from "schema-dts";
|
||||
import type { ProfilePage, WebPage } from "schema-dts";
|
||||
import { personId, websiteId } from "./ids";
|
||||
|
||||
export type WebsiteSchemaParams = {
|
||||
readonly description: string;
|
||||
readonly lang: string;
|
||||
readonly mainEntityId?: string;
|
||||
readonly page: string;
|
||||
readonly siteUrl: string;
|
||||
readonly title: string;
|
||||
readonly lang: string;
|
||||
readonly type?: "WebPage" | "ProfilePage";
|
||||
};
|
||||
|
||||
export default ({ siteUrl, page, title, description, lang }: WebsiteSchemaParams): WithContext<WebPage> => ({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebPage",
|
||||
"@id": new URL(page, siteUrl).toString(),
|
||||
"url": new URL(page, siteUrl).toString(),
|
||||
"name": title,
|
||||
"description": description,
|
||||
"inLanguage": lang,
|
||||
"mainEntity": {
|
||||
"@type": "WebSite",
|
||||
"@id": new URL("/", siteUrl).toString(),
|
||||
},
|
||||
});
|
||||
export default ({ siteUrl, page, title, description, lang, type = "WebPage", mainEntityId }: WebsiteSchemaParams): WebPage | ProfilePage => {
|
||||
const url = new URL(page, siteUrl).toString();
|
||||
|
||||
const base = {
|
||||
"@type": type,
|
||||
"@id": url,
|
||||
"url": url,
|
||||
"name": title,
|
||||
"description": description,
|
||||
"inLanguage": lang,
|
||||
"isPartOf": { "@id": websiteId(siteUrl) },
|
||||
} as const;
|
||||
|
||||
if (type === "ProfilePage") {
|
||||
return {
|
||||
...base,
|
||||
"@type": "ProfilePage",
|
||||
"mainEntity": { "@id": mainEntityId ?? personId(siteUrl) },
|
||||
};
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Person } from "schema-dts";
|
||||
import { config } from "../../config";
|
||||
import { personId } from "./ids";
|
||||
|
||||
export type PersonSchemaParams = {
|
||||
readonly siteUrl: string;
|
||||
};
|
||||
|
||||
export default ({ siteUrl }: PersonSchemaParams): Person => ({
|
||||
"@type": "Person",
|
||||
"@id": personId(siteUrl),
|
||||
"name": config.author.name,
|
||||
"url": config.author.url,
|
||||
"email": config.author.email,
|
||||
"image": new URL(config.og.defaultPreview, siteUrl).toString(),
|
||||
"sameAs": config.author.sameAs,
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { WebSite } from "schema-dts";
|
||||
import { config } from "../../config";
|
||||
import { personId, websiteId } from "./ids";
|
||||
|
||||
export type WebsiteSchemaParams = {
|
||||
readonly description: string;
|
||||
readonly lang: string;
|
||||
readonly name: string;
|
||||
readonly siteUrl: string;
|
||||
};
|
||||
|
||||
export default ({ siteUrl, name, description, lang }: WebsiteSchemaParams): WebSite => ({
|
||||
"@type": "WebSite",
|
||||
"@id": websiteId(siteUrl),
|
||||
"url": siteUrl,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"inLanguage": lang,
|
||||
"publisher": { "@id": personId(siteUrl) },
|
||||
"author": { "@id": personId(siteUrl) },
|
||||
"copyrightHolder": { "@id": personId(siteUrl) },
|
||||
"sameAs": config.author.sameAs,
|
||||
});
|
||||
Reference in New Issue
Block a user