SEO revamped from RodneyLabs - TS support
This commit is contained in:
parent
6ce876ab03
commit
dda41c8414
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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} />
|
Loading…
Reference in New Issue