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