SEO revamped from RodneyLabs - TS support

This commit is contained in:
matthieu42morin 2024-04-28 01:20:13 +02:00
parent 6ce876ab03
commit dda41c8414
4 changed files with 398 additions and 0 deletions

View File

@ -0,0 +1,53 @@
<script lang="ts">
import { website } from '$lib/config';
import type { Tag } from '$lib/types/post';
export let article = false;
export let datePublished: string | null = null;
export let lastUpdated: string | null = null;
export let featuredImage: string;
export let featuredImageAlt: string;
export let squareImage: string;
export let metadescription: string;
export let ogLanguage: string;
export let pageTitle: string;
export let siteTitle: string;
export let url: string;
export let tags: Tag[];
</script>
<svelte:head>
<meta property="og:site_name" content={siteTitle} />
<meta property="og:locale" content={ogLanguage} />
<meta property="og:url" content={url} />
<meta property="og:type" content={article ? 'article' : 'website'} />
<meta property="og:title" content={pageTitle} />
{#if metadescription}
<meta property="og:description" content={metadescription} />
{:else}
<meta name="og:description" content={website.description} />
{/if}
{#if featuredImage}
<meta property="og:image" content={featuredImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="627" />
<meta property="og:image:alt" content={featuredImageAlt} />
{/if}
{#if squareImage}
<meta property="og:image" content={squareImage} />
<meta property="og:image:width" content="400" />
<meta property="og:image:height" content="400" />
<meta property="og:image:alt" content={featuredImageAlt} />
{/if}
{#if article}
<!-- <meta property="article:publisher" content={facebookPage} />
<meta property="article:author" content={facebookAuthorPage} /> -->
<meta property="article:published_time" content={datePublished} />
<meta property="article:modified_time" content={lastUpdated} />
{/if}
{#if tags && tags.length > 0}
{#each tags as tag (tag)}
<meta property="article:tag" content={tag} />
{/each}
{/if}
</svelte:head>

View File

@ -0,0 +1,207 @@
<script lang="ts">
import hash from 'object-hash';
export let article = false;
export let author;
export let breadcrumbs: { name: string; slug: string }[] = [];
export let datePublished;
export let entity;
export let lastUpdated;
export let featuredImage;
export let metadescription;
export let siteLanguage;
export let siteTitle;
export let siteTitleAlt;
export let siteUrl = '';
export let title;
export let url;
export let githubPage;
export let linkedinProfile;
export let telegramUsername;
export let twitterUsername;
/**
* @type {{ url: string; faviconWidth: number; faviconHeight: number } | null}
*/
export let entityMeta = null;
const entityHash = hash({ author }, { algorithm: 'md5' });
const schemaOrgEntity =
entityMeta !== null
? {
'@type': ['Person', 'Organization'],
'@id': `${siteUrl}/#/schema/person/${entityHash}`,
name: author,
image: {
'@type': 'ImageObject',
'@id': `${siteUrl}/#personlogo`,
inLanguage: siteLanguage,
url: entityMeta.url,
width: entityMeta.faviconWidth,
height: entityMeta.faviconHeight,
caption: author
},
logo: {
'@id': `${siteUrl}/#personlogo`
},
sameAs: [
`https://twitter.com/${twitterUsername}`,
`https://github.com/${githubPage}`,
`https://t.me/${telegramUsername}`,
`https://linkedin.com/in/${linkedinProfile}`
]
}
: null;
const schemaOrgWebsite = {
'@type': 'WebSite',
'@id': `${siteUrl}/#website`,
url: siteUrl,
name: siteTitle,
description: siteTitleAlt,
publisher: {
'@id': `${siteUrl}/#/schema/person/${entityHash}`
},
potentialAction: [
{
'@type': 'SearchAction',
target: `${siteUrl}/?s={search_term_string}`,
'query-input': 'required name=search_term_string'
}
],
inLanguage: siteLanguage
};
const schemaOrgImageObject = {
'@type': 'ImageObject',
'@id': `${url}#primaryimage`,
inLanguage: siteLanguage,
url: featuredImage.url,
contentUrl: featuredImage.url,
width: featuredImage.width,
height: featuredImage.height,
caption: featuredImage.caption
};
const schemaOrgBreadcrumbList = {
'@type': 'BreadcrumbList',
'@id': `${url}#breadcrumb`,
itemListElement: breadcrumbs.map((element, index) => ({
'@type': 'ListItem',
position: index + 1,
item: {
'@type': 'WebPage',
'@id': `${siteUrl}/${element.slug}`,
url: `${siteUrl}/${element.slug}`,
name: element.name
}
}))
};
const schemaOrgWebPage = {
'@type': 'WebPage',
'@id': `${url}#webpage`,
url,
name: title,
isPartOf: {
'@id': `${siteUrl}/#website`
},
primaryImageOfPage: {
'@id': `${url}#primaryimage`
},
datePublished,
dateModified: lastUpdated,
author: {
'@id': `${siteUrl}/#/schema/person/${entityHash}`
},
description: metadescription,
breadcrumb: {
'@id': `${url}#breadcrumb`
},
inLanguage: siteLanguage,
potentialAction: [
{
'@type': 'ReadAction',
target: [url]
}
]
};
let schemaOrgArticle = null;
if (article) {
schemaOrgArticle = {
'@type': 'Article',
'@id': `${url}#article`,
isPartOf: {
'@id': `${url}#webpage`
},
author: {
'@id': `${siteUrl}/#/schema/person/${entityHash}`
},
headline: title,
datePublished,
dateModified: lastUpdated,
mainEntityOfPage: {
'@id': `${url}#webpage`
},
publisher: {
'@id': `${siteUrl}/#/schema/person/${entityHash}`
},
image: {
'@id': `${url}#primaryimage`
},
articleSection: ['blog'],
inLanguage: siteLanguage
};
}
const schemaOrgPublisher = {
'@type': ['Person', 'Organization'],
'@id': `${siteUrl}/#/schema/person/${entityHash}`,
name: entity,
image: {
'@type': 'ImageObject',
'@id': `${siteUrl}/#personlogo`,
inLanguage: siteLanguage,
url: `${siteUrl}/assets/rodneylab-logo.png`,
contentUrl: `${siteUrl}/assets/rodneylab-logo.png`,
width: 512,
height: 512,
caption: entity
},
logo: {
'@id': `${siteUrl}/#personlogo`
},
sameAs: [
`https://twitter.com/${twitterUsername}`,
`https://github.com/${githubPage}`,
`https://t.me/${telegramUsername}`,
`https://linkedin.com/in/${linkedinProfile}`
]
};
const schemaOrgArray = [
schemaOrgEntity,
schemaOrgWebsite,
schemaOrgImageObject,
schemaOrgWebPage,
schemaOrgBreadcrumbList,
...(article ? [schemaOrgArticle] : []),
schemaOrgPublisher
];
const schemaOrgObject = {
'@context': 'https://schema.org',
'@graph': schemaOrgArray
};
let jsonLdString = JSON.stringify(schemaOrgObject);
let jsonLdScript = `
<script type="application/ld+json">
${jsonLdString}
${'<'}/script>
`;
</script>
<svelte:head>
{@html jsonLdScript}
</svelte:head>

View File

@ -0,0 +1,32 @@
<script>
export let article = false;
export let author;
export let twitterUsername;
export let image;
export let timeToRead = 0;
/*
* When there is an equivalent og tag present, Twitter takes that so check OpenGraph before
* adding additional tags, unless you want to override OpenGraph.
*/
</script>
<svelte:head>
<meta name="twitter:card" content="summary_large_image" />
{#if image}
<meta name="twitter:image" content={image.url} />
{/if}
{#if twitterUsername}
<meta name="twitter:creator" content={`@${twitterUsername}`} />
<meta name="twitter:site" content={`@${twitterUsername}`} />
{/if}
<meta name="twitter:label1" content="Written by" />
<meta name="twitter:data1" content={author} />
{#if article && timeToRead > 0}
<meta name="twitter:label2" content="Est. reading time" />
<meta
name="twitter:data2"
content={timeToRead !== 1 ? `${timeToRead} minutes` : '1 minute'}
/>
{/if}
</svelte:head>

View File

@ -0,0 +1,106 @@
<script lang="ts">
import defaultFeaturedImage from '$lib/assets/home/home.jpg';
import defaultOgImage from '$lib/assets/home/home-open-graph.jpg';
import defaultOgSquareImage from '$lib/assets/home/home-open-graph-square.jpg';
import defaultTwitterImage from '$lib/assets/home/home-twitter.jpg';
import { website } from '$lib/config';
import { VERTICAL_LINE_ENTITY } from '$lib/constants';
import OpenGraph from './OG.svelte';
import SchemaOrg from './SchemaOrg.svelte';
import Twitter from './Twitter.svelte';
import type { Tag } from '$lib/types/post';
const {
author,
entity,
ogLanguage,
siteLanguage,
siteShortTitle,
siteTitle,
siteUrl,
githubPage,
linkedinProfile,
telegramUsername,
twitterUsername
} = website;
export let article = false;
export let breadcrumbs: { name: string; slug: string }[] = [];
export let entityMeta = null;
export let lastUpdated;
export let datePublished;
export let metadescription: string;
export let tags: Tag[];
export let slug: string;
export let timeToRead = 0;
export let title: string;
const defaultAlt =
'picture of a person with long, curly hair, wearing a red had taking a picture with an analogue camera';
// imported props with fallback defaults
export let featuredImage: string;
export let featuredImageAlt: string | undefined = defaultAlt;
export let ogImage: string;
export let ogSquareImage: string;
export let twitterImage: string;
const url = `${siteUrl}/${slug}`;
const pageTitle = `${siteTitle} ${VERTICAL_LINE_ENTITY} ${title}`;
const openGraphProps = {
article,
datePublished,
lastUpdated,
image: ogImage,
squareImage: ogSquareImage,
metadescription,
ogLanguage,
pageTitle,
siteTitle,
url,
tags,
...(article ? { datePublished, lastUpdated } : {})
};
const schemaOrgProps = {
article,
author,
breadcrumbs,
datePublished,
entity,
lastUpdated,
entityMeta,
featuredImage,
metadescription,
siteLanguage,
siteTitle,
siteTitleAlt: siteShortTitle,
siteUrl,
title: pageTitle,
url,
githubPage,
linkedinProfile,
twitterUsername,
telegramUsername
};
const twitterProps = {
article,
author,
twitterUsername,
image: twitterImage,
timeToRead
};
</script>
<svelte:head>
<title>{pageTitle}</title>
<meta name="description" content={metadescription} />
<meta
name="robots"
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
/>
<link rel="canonical" href={url} />
</svelte:head>
<Twitter {...twitterProps} />
<OpenGraph {...openGraphProps} />
<SchemaOrg {...schemaOrgProps} />