Skip to content

Commit 6657821

Browse files
committed
feat: SEO improvements + astro upgrade (#28)
* feat: SEO improvements + astro upgrade * chore: lint fix
1 parent b5e4d30 commit 6657821

9 files changed

Lines changed: 535 additions & 374 deletions

File tree

.claude/settings.local.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(pnpm build:*)",
5+
"Bash(pnpm exec astro build:*)",
6+
"Bash(pnpm install:*)"
7+
]
8+
}
9+
}

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "spiffy-shepherd",
2+
"name": "nodejs-design-patterns-site",
33
"type": "module",
4-
"version": "0.0.1",
4+
"version": "4.0.0",
55
"scripts": {
66
"dev": "astro dev",
77
"build": "astro build",
@@ -15,16 +15,16 @@
1515
},
1616
"dependencies": {
1717
"@astrojs/partytown": "^2.1.4",
18-
"@astrojs/react": "^4.3.1",
19-
"@astrojs/sitemap": "^3.6.0",
18+
"@astrojs/react": "^4.4.2",
19+
"@astrojs/sitemap": "^3.7.0",
2020
"@expressive-code/plugin-collapsible-sections": "^0.41.3",
2121
"@lucide/astro": "^0.522.0",
2222
"@radix-ui/react-slot": "^1.2.3",
2323
"@tailwindcss/typography": "^0.5.16",
2424
"@tailwindcss/vite": "^4.1.8",
2525
"@types/react": "^19.1.6",
2626
"@types/react-dom": "^19.1.5",
27-
"astro": "^5.13.10",
27+
"astro": "^5.16.16",
2828
"astro-expressive-code": "^0.41.3",
2929
"astro-masonry": "^1.2.2",
3030
"class-variance-authority": "^0.7.1",

pnpm-lock.yaml

Lines changed: 409 additions & 367 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Layout.astro

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface Props {
2626
ogDescription?: string
2727
ogImage?: string
2828
canonical?: string
29+
additionalSchema?: object
30+
noindex?: boolean
2931
}
3032
3133
const {
@@ -35,6 +37,8 @@ const {
3537
ogDescription = OG_DESCRIPTION,
3638
ogImage = OG_IMAGE,
3739
canonical,
40+
additionalSchema,
41+
noindex = false,
3842
} = Astro.props
3943
---
4044

@@ -59,7 +63,13 @@ const {
5963
name="keywords"
6064
content="Node.js, Design Patterns, JavaScript, Backend Development, Software Architecture, Programming, Web Development, Scalable Applications"
6165
/>
62-
<meta name="robots" content="index, follow" />
66+
{
67+
noindex ? (
68+
<meta name="robots" content="noindex, nofollow" />
69+
) : (
70+
<meta name="robots" content="index, follow" />
71+
)
72+
}
6373
<meta name="theme-color" content={THEME_COLOR} />
6474

6575
<!-- Canonical URL -->
@@ -132,6 +142,15 @@ const {
132142
}
133143
</script>
134144

145+
{
146+
additionalSchema && (
147+
<script
148+
type="application/ld+json"
149+
set:html={JSON.stringify(additionalSchema)}
150+
/>
151+
)
152+
}
153+
135154
<script>
136155
import { initTheme } from './lib/theme.ts'
137156
initTheme()

src/components/blog/BlogLayout.astro

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Footer from '@components/Footer.astro'
88
import BookPromo from '@components/blog/BookPromo.astro'
99
import { List } from '@lucide/astro'
1010
import { calculateReadingTime, formatDate } from '@lib/utils'
11+
import { generateBlogPostingSchema } from '@lib/schema'
1112
import Breadcrumb from './Breadcrumb.astro'
1213
import BlogCard from './BlogCard.astro'
1314
@@ -41,6 +42,18 @@ const formattedUpdatedDate =
4142
4243
const readingTime = calculateReadingTime(post.body as string)
4344
45+
const wordCount = (post.body as string).split(/\s+/).length
46+
const blogPostingSchema = generateBlogPostingSchema({
47+
title,
48+
description,
49+
datePublished: date,
50+
dateModified: updatedAt || date,
51+
authors,
52+
url: `${SITE_URL}/blog/${post.id}/`,
53+
wordCount,
54+
siteUrl: SITE_URL,
55+
})
56+
4457
// Groups ToC in a way that it's easier to render as nested lists
4558
const nestedToc: NestedCollectionHeadingsItem[] = []
4659
let lastNestedTocEntry: NestedCollectionHeadingsItem | null = null
@@ -65,6 +78,7 @@ if (post.rendered?.metadata?.headings) {
6578
ogTitle={title}
6679
ogDescription={description}
6780
canonical={`${SITE_URL}/blog/${post.id}/`}
81+
additionalSchema={blogPostingSchema}
6882
>
6983
<main class="min-h-screen bg-base-100">
7084
<!-- Blog Header -->

src/components/pages/Home/Faq.astro

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { Picture } from 'astro:assets'
33
import manReading from '@images/mktg/young-man-reading-nodejs-design-patterns.jpg'
44
import { getCollection, render } from 'astro:content'
5+
import { generateFAQPageSchema } from '@lib/schema'
56
67
const faq = await getCollection('faq')
78
const renderedFaq = await Promise.all(
@@ -13,12 +14,27 @@ const renderedFaq = await Promise.all(
1314
...f,
1415
Content: renderedContent.Content,
1516
data: f.data,
17+
plainTextAnswer: f.body
18+
? f.body
19+
.replace(/\*\*(.*?)\*\*/g, '$1')
20+
.replace(/\*(.*?)\*/g, '$1')
21+
.replace(/\[(.*?)\]\(.*?\)/g, '$1')
22+
.replace(/#+\s/g, '')
23+
.trim()
24+
: '',
1625
}
1726
}),
1827
)
28+
29+
const faqSchemaItems = renderedFaq.map((item) => ({
30+
question: item.data.question,
31+
answer: item.plainTextAnswer,
32+
}))
33+
const faqPageSchema = generateFAQPageSchema(faqSchemaItems)
1934
---
2035

2136
<section id="faq" class="bg-base-100 py-16 lg:py-24">
37+
<script type="application/ld+json" set:html={JSON.stringify(faqPageSchema)} />
2238
<div class="max-w-7xl mx-auto px-6">
2339
<div class="max-w-4xl mx-auto text-center mb-16">
2440
<h2

src/components/pages/Home/Hero.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ const numberFormatter = new Intl.NumberFormat('en-US', {
9393
class: 'w-full max-w-sm lg:max-w-md',
9494
}}
9595
class="w-full max-w-sm lg:max-w-md drop-shadow-2xl shadow-2xl lg:absolute rounded-tr-sm rounded-br-sm"
96-
loading="lazy"
96+
loading="eager"
97+
fetchpriority="high"
9798
width={1000}
9899
height={1234}
99100
/>

src/lib/schema.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { CollectionEntry } from 'astro:content'
2+
3+
export interface BlogPostingSchemaOptions {
4+
title: string
5+
description: string
6+
datePublished: Date
7+
dateModified: Date
8+
authors: CollectionEntry<'authors'>[]
9+
url: string
10+
wordCount: number
11+
siteUrl: string
12+
}
13+
14+
export function generateBlogPostingSchema(
15+
options: BlogPostingSchemaOptions,
16+
): object {
17+
return {
18+
'@context': 'https://schema.org',
19+
'@type': 'BlogPosting',
20+
headline: options.title,
21+
description: options.description,
22+
datePublished: options.datePublished.toISOString(),
23+
dateModified: options.dateModified.toISOString(),
24+
author: options.authors.map((author) => ({
25+
'@type': 'Person',
26+
name: author.data.name,
27+
url: author.data.link,
28+
})),
29+
publisher: {
30+
'@type': 'Organization',
31+
name: 'Node.js Design Patterns',
32+
url: options.siteUrl,
33+
logo: {
34+
'@type': 'ImageObject',
35+
url: `${options.siteUrl}/images/og-image.jpg`,
36+
},
37+
},
38+
mainEntityOfPage: { '@type': 'WebPage', '@id': options.url },
39+
wordCount: options.wordCount,
40+
inLanguage: 'en-US',
41+
}
42+
}
43+
44+
export interface FAQItem {
45+
question: string
46+
answer: string
47+
}
48+
49+
export function generateFAQPageSchema(items: FAQItem[]): object {
50+
return {
51+
'@context': 'https://schema.org',
52+
'@type': 'FAQPage',
53+
mainEntity: items.map((item) => ({
54+
'@type': 'Question',
55+
name: item.question,
56+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
57+
})),
58+
}
59+
}

src/pages/404.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const pageDescription =
1313
description={pageDescription}
1414
ogTitle={pageTitle}
1515
ogDescription={pageDescription}
16+
noindex={true}
1617
>
1718
<main class="min-h-screen bg-base-100 relative overflow-hidden">
1819
<!-- ASCII Text Background -->

0 commit comments

Comments
 (0)