Skip to content

Commit 4abef83

Browse files
Fixes for SEO indexing
1 parent 835c8fd commit 4abef83

4 files changed

Lines changed: 177 additions & 9 deletions

File tree

public/robots.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
User-agent: *
2+
Allow: /
3+
4+
Sitemap: https://devproxy.net/sitemap-index.xml

src/layouts/Layout.astro

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,22 @@ interface Props {
66
title: string;
77
description?: string;
88
image?: string;
9+
ogType?: 'website' | 'article';
10+
structuredData?: Record<string, unknown> | Record<string, unknown>[];
911
}
1012
11-
const { title, description = 'Dev Proxy - Simulate API behaviors for testing and development', image } = Astro.props;
13+
const {
14+
title,
15+
description = 'Dev Proxy - Simulate API behaviors for testing and development',
16+
image,
17+
ogType = 'website',
18+
structuredData,
19+
} = Astro.props;
1220
const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? 'https://devproxy.net');
1321
const ogImage = image ? new URL(image, Astro.site ?? 'https://devproxy.net').href : undefined;
22+
const structuredDataJson = structuredData
23+
? JSON.stringify(structuredData).replace(/</g, '\\u003c')
24+
: undefined;
1425
---
1526

1627
<!doctype html>
@@ -22,10 +33,11 @@ const ogImage = image ? new URL(image, Astro.site ?? 'https://devproxy.net').hre
2233
<meta name="description" content={description} />
2334
<meta name="generator" content={Astro.generator} />
2435
<title>{title} - Dev Proxy</title>
36+
<link rel="canonical" href={canonicalURL.href} />
2537
<link rel="alternate" type="application/rss+xml" title="Dev Proxy Blog" href={`${import.meta.env.BASE_URL}rss.xml`} />
2638

2739
<!-- Open Graph -->
28-
<meta property="og:type" content="website" />
40+
<meta property="og:type" content={ogType} />
2941
<meta property="og:title" content={`${title} - Dev Proxy`} />
3042
<meta property="og:description" content={description} />
3143
<meta property="og:url" content={canonicalURL.href} />
@@ -36,6 +48,7 @@ const ogImage = image ? new URL(image, Astro.site ?? 'https://devproxy.net').hre
3648
<meta name="twitter:title" content={`${title} - Dev Proxy`} />
3749
<meta name="twitter:description" content={description} />
3850
{ogImage && <meta name="twitter:image" content={ogImage} />}
51+
{structuredDataJson && <script is:inline type="application/ld+json" set:html={structuredDataJson} />}
3952
<script is:inline>
4053
(function() {
4154
const theme = localStorage.getItem('theme');

src/pages/blog/[slug].astro

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,82 @@ import Layout from '../../layouts/Layout.astro';
33
import { getCollection, render } from 'astro:content';
44
55
export async function getStaticPaths() {
6-
const posts = await getCollection('blog');
6+
const posts = (await getCollection('blog')).sort(
7+
(a, b) => b.data.date.valueOf() - a.data.date.valueOf()
8+
);
9+
710
return posts.map((post) => ({
811
params: { slug: post.id },
9-
props: { post },
12+
props: {
13+
post,
14+
relatedPosts: posts
15+
.filter(candidate => candidate.id !== post.id)
16+
.sort((a, b) => {
17+
const aSharedTags = a.data.tags.filter(tag => post.data.tags.includes(tag)).length;
18+
const bSharedTags = b.data.tags.filter(tag => post.data.tags.includes(tag)).length;
19+
return bSharedTags - aSharedTags || b.data.date.valueOf() - a.data.date.valueOf();
20+
})
21+
.slice(0, 3),
22+
},
1023
}));
1124
}
1225
13-
const { post } = Astro.props;
26+
const { post, relatedPosts } = Astro.props;
1427
const { Content } = await render(post);
1528
1629
// Estimate reading time (~200 words per minute)
1730
const wordCount = post.body?.split(/\s+/).length ?? 0;
1831
const readingTime = Math.max(1, Math.ceil(wordCount / 200));
32+
const siteURL = Astro.site ?? new URL('https://devproxy.net');
33+
const postURL = new URL(Astro.url.pathname, siteURL).href;
34+
const blogURL = new URL(`${import.meta.env.BASE_URL}blog/`, siteURL).href;
35+
const structuredData = [
36+
{
37+
'@context': 'https://schema.org',
38+
'@type': 'BlogPosting',
39+
headline: post.data.title,
40+
description: post.data.description,
41+
datePublished: post.data.date.toISOString(),
42+
author: {
43+
'@type': 'Person',
44+
name: post.data.author,
45+
},
46+
image: post.data.image ? new URL(post.data.image, siteURL).href : undefined,
47+
mainEntityOfPage: postURL,
48+
publisher: {
49+
'@type': 'Organization',
50+
name: 'Dev Proxy',
51+
url: new URL(import.meta.env.BASE_URL, siteURL).href,
52+
},
53+
},
54+
{
55+
'@context': 'https://schema.org',
56+
'@type': 'BreadcrumbList',
57+
itemListElement: [
58+
{
59+
'@type': 'ListItem',
60+
position: 1,
61+
name: 'Blog',
62+
item: blogURL,
63+
},
64+
{
65+
'@type': 'ListItem',
66+
position: 2,
67+
name: post.data.title,
68+
item: postURL,
69+
},
70+
],
71+
},
72+
];
1973
---
2074

21-
<Layout title={post.data.title} description={post.data.description} image={post.data.image}>
75+
<Layout
76+
title={post.data.title}
77+
description={post.data.description}
78+
image={post.data.image}
79+
ogType="article"
80+
structuredData={structuredData}
81+
>
2282
<article class="pt-16 pb-24 px-4">
2383
<div class="max-w-3xl mx-auto">
2484

@@ -62,6 +122,26 @@ const readingTime = Math.max(1, Math.ceil(wordCount / 200));
62122
<Content />
63123
</div>
64124

125+
<aside class="mt-16 pt-8 border-t" style="border-color: var(--border-primary);" aria-labelledby="related-posts-heading">
126+
<h2 id="related-posts-heading" class="text-xl font-bold mb-6">Related posts</h2>
127+
<div class="grid md:grid-cols-3 gap-4">
128+
{relatedPosts.map(relatedPost => (
129+
<a
130+
href={`${import.meta.env.BASE_URL}blog/${relatedPost.id}/`}
131+
class="group block rounded-2xl border p-5 transition-all duration-300 hover:border-purple-500/50"
132+
style="background: var(--bg-secondary); border-color: var(--border-primary);"
133+
>
134+
<h3 class="font-semibold leading-snug group-hover:text-purple-400 transition-colors">
135+
{relatedPost.data.title}
136+
</h3>
137+
<time class="block text-xs mt-3" style="color: var(--text-faint);" datetime={relatedPost.data.date.toISOString()}>
138+
{relatedPost.data.date.toLocaleDateString('en-GB', { year: 'numeric', month: 'long', day: 'numeric' })}
139+
</time>
140+
</a>
141+
))}
142+
</div>
143+
</aside>
144+
65145
<!-- Post footer -->
66146
<footer class="mt-16 pt-8 border-t" style="border-color: var(--border-primary);">
67147
<div class="flex items-center justify-between flex-wrap gap-4">

src/pages/samples/[slug].astro

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,70 @@ import { getCollection, render } from 'astro:content';
44
55
export async function getStaticPaths() {
66
const samples = await getCollection('samples');
7+
78
return samples.map((sample) => ({
89
params: { slug: sample.id },
9-
props: { sample },
10+
props: {
11+
sample,
12+
relatedSamples: samples
13+
.filter(candidate => candidate.id !== sample.id)
14+
.sort((a, b) => {
15+
const aSharedProducts = a.data.products.filter(product => sample.data.products.includes(product)).length;
16+
const bSharedProducts = b.data.products.filter(product => sample.data.products.includes(product)).length;
17+
return bSharedProducts - aSharedProducts
18+
|| new Date(b.data.updateDateTime).valueOf() - new Date(a.data.updateDateTime).valueOf();
19+
})
20+
.slice(0, 3),
21+
},
1022
}));
1123
}
1224
13-
const { sample } = Astro.props;
25+
const { sample, relatedSamples } = Astro.props;
1426
const { data } = sample;
1527
const { Content } = await render(sample);
1628
const ogImage = data.thumbnails.find(t => t.type === 'image')?.url;
29+
const siteURL = Astro.site ?? new URL('https://devproxy.net');
30+
const sampleURL = new URL(Astro.url.pathname, siteURL).href;
31+
const samplesURL = new URL(`${import.meta.env.BASE_URL}samples/`, siteURL).href;
32+
const structuredData = [
33+
{
34+
'@context': 'https://schema.org',
35+
'@type': 'CreativeWork',
36+
name: data.title,
37+
description: data.shortDescription,
38+
url: sampleURL,
39+
dateCreated: new Date(data.creationDateTime).toISOString(),
40+
dateModified: new Date(data.updateDateTime).toISOString(),
41+
image: ogImage,
42+
author: data.authors.map(author => ({
43+
'@type': 'Person',
44+
name: author.name,
45+
sameAs: `https://github.com/${author.gitHubAccount}`,
46+
})),
47+
isBasedOn: data.url,
48+
},
49+
{
50+
'@context': 'https://schema.org',
51+
'@type': 'BreadcrumbList',
52+
itemListElement: [
53+
{
54+
'@type': 'ListItem',
55+
position: 1,
56+
name: 'Samples',
57+
item: samplesURL,
58+
},
59+
{
60+
'@type': 'ListItem',
61+
position: 2,
62+
name: data.title,
63+
item: sampleURL,
64+
},
65+
],
66+
},
67+
];
1768
---
1869

19-
<Layout title={data.title} description={data.shortDescription} image={ogImage}>
70+
<Layout title={data.title} description={data.shortDescription} image={ogImage} structuredData={structuredData}>
2071
<section class="pt-16 pb-24 px-4">
2172
<div class="max-w-4xl mx-auto">
2273

@@ -103,6 +154,26 @@ const ogImage = data.thumbnails.find(t => t.type === 'image')?.url;
103154
</div>
104155
)}
105156

157+
<aside class="mt-16 pt-8 border-t" style="border-color: var(--border-primary);" aria-labelledby="related-samples-heading">
158+
<h2 id="related-samples-heading" class="text-xl font-bold mb-6">Related samples</h2>
159+
<div class="grid md:grid-cols-3 gap-4">
160+
{relatedSamples.map(relatedSample => (
161+
<a
162+
href={`${import.meta.env.BASE_URL}samples/${relatedSample.id}/`}
163+
class="group block rounded-2xl border p-5 transition-all duration-300 hover:border-purple-500/50"
164+
style="background: var(--bg-secondary); border-color: var(--border-primary);"
165+
>
166+
<h3 class="font-semibold leading-snug group-hover:text-purple-400 transition-colors">
167+
{relatedSample.data.title}
168+
</h3>
169+
<p class="text-sm mt-3 leading-relaxed line-clamp-3" style="color: var(--text-muted);">
170+
{relatedSample.data.shortDescription}
171+
</p>
172+
</a>
173+
))}
174+
</div>
175+
</aside>
176+
106177
<!-- Footer with dates -->
107178
<footer class="mt-16 pt-8 border-t flex items-center justify-between flex-wrap gap-4" style="border-color: var(--border-primary);">
108179
<div class="flex gap-6 text-xs" style="color: var(--text-faint);">

0 commit comments

Comments
 (0)