Skip to content

Commit 0170be9

Browse files
authored
Feat/content calendar (#29)
* feat: Content calendar and new blog article * feat: article improvements after review * chore: lint fix * chore: format * chore: publish date
1 parent fef171a commit 0170be9

14 files changed

Lines changed: 1514 additions & 47 deletions

File tree

.claude/article-brief-template.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Article Brief Template
2+
3+
Use this template when planning new blog articles for Node.js Design Patterns.
4+
5+
---
6+
7+
## [Article Title]
8+
9+
**Target keyword:** [primary keyword]
10+
**Secondary keywords:** [comma-separated list]
11+
**Estimated search volume:** [monthly searches]
12+
**Search intent:** [informational / tutorial / reference]
13+
**Buyer stage:** [awareness / consideration / implementation]
14+
**Content pillar:** [Core APIs / Modern Features / Patterns & Architecture]
15+
16+
---
17+
18+
### Outline
19+
20+
1. **Introduction**
21+
- Problem statement / why this matters
22+
- What readers will learn
23+
24+
2. **Quick Answer** (for featured snippets)
25+
- 2-3 sentence summary
26+
- Code snippet if applicable
27+
28+
3. **Main Content**
29+
- Section 1: [topic]
30+
- Section 2: [topic]
31+
- Section 3: [topic]
32+
- (Add more sections as needed)
33+
34+
4. **Best Practices / Common Mistakes**
35+
- List of dos and don'ts
36+
- Common pitfalls to avoid
37+
38+
5. **Related Book Content** (if applicable)
39+
- Light excerpt or reference to relevant chapter
40+
- Connection to book patterns
41+
42+
6. **FAQ Section**
43+
- 3-5 frequently asked questions
44+
- Keep answers concise (2-3 sentences)
45+
46+
7. **Conclusion**
47+
- Summary of key points
48+
- CTA: Free chapter download
49+
50+
---
51+
52+
### Code Examples Checklist
53+
54+
- [ ] Modern ESM syntax (`import`/`export`)
55+
- [ ] `async`/`await` patterns (avoid callbacks)
56+
- [ ] Proper error handling included
57+
- [ ] Practical, real-world examples
58+
- [ ] Comments explain non-obvious code
59+
- [ ] No external dependencies unless necessary
60+
61+
---
62+
63+
### Internal Links
64+
65+
Link to related existing articles:
66+
67+
- [ ] [Article 1 title](/blog/slug)
68+
- [ ] [Article 2 title](/blog/slug)
69+
70+
Link to book chapters:
71+
72+
- [ ] [Relevant chapter](/)
73+
74+
---
75+
76+
### SEO Checklist
77+
78+
- [ ] Keyword in title
79+
- [ ] Keyword in H1
80+
- [ ] Keyword in first paragraph
81+
- [ ] Keyword in at least one H2
82+
- [ ] Meta description (150-160 characters)
83+
- [ ] Alt text for all images
84+
- [ ] FAQ section with 3-5 questions (for FAQ schema)
85+
- [ ] Internal links to 2+ related articles
86+
- [ ] External links to official docs where relevant
87+
88+
---
89+
90+
### Frontmatter Template
91+
92+
```yaml
93+
---
94+
date: YYYY-MM-DDTHH:MM:SS
95+
updatedAt: YYYY-MM-DDTHH:MM:SS
96+
title: [Article Title]
97+
slug: [url-friendly-slug]
98+
description: [150-160 character meta description with target keyword]
99+
authors: ['luciano-mammino']
100+
tags: ['blog']
101+
faq:
102+
- question: [FAQ Question 1]?
103+
answer: [Concise 2-3 sentence answer]
104+
- question: [FAQ Question 2]?
105+
answer: [Concise 2-3 sentence answer]
106+
- question: [FAQ Question 3]?
107+
answer: [Concise 2-3 sentence answer]
108+
---
109+
```
110+
111+
---
112+
113+
### Content Calendar Reference
114+
115+
| Month | # | Article | Primary Keyword | Est. Volume |
116+
| ----- | --- | -------------------------------- | ------------------------------- | ----------- |
117+
| 1-2 | 1 | HTTP request in Node.js | "node js http request" | 15,000+ |
118+
| 1-2 | 2 | Environment variables in Node.js | "node js environment variables" | 8,000+ |
119+
| 1-2 | 3 | Using Node.js built-in SQLite | "node js sqlite" | 5,000+ |
120+
| 1-2 | 4 | Writing a CLI with Node.js | "node js cli" | 4,000+ |
121+
| 3-4 | 5 | API server in Node.js (no deps) | "node js http server" | 6,000+ |
122+
| 3-4 | 6 | Hashing files with Node.js | "node js hash file" | 2,000+ |
123+
| 3-4 | 7 | Base64 encode/decode | "base64 node js" | 3,000+ |
124+
| 3-4 | 8 | Files and paths (node:path) | "node js path" | 4,000+ |
125+
| 5-6 | 9 | Node.js Event Emitter | "node js event emitter" | 6,000+ |
126+
| 5-6 | 10 | Interactive streams guide | "node js streams" | 10,000+ |
127+
| 5-6 | 11 | How CommonJS works | "commonjs node js" | 3,000+ |
128+
| 5-6 | 12 | Node.js console tips | "node js console" | 2,000+ |
129+
| 7-8 | 13 | Type-stripping in Node.js | "node js typescript" | Growing |
130+
| 7-8 | 14 | Node.js built-in test runner | "node js test runner" | 3,000+ |
131+
| 7-8 | 15 | Import maps in Node.js | "node js import maps" | 1,000+ |
132+
| 7-8 | 16 | Encrypting files with Node.js | "node js encrypt file" | 1,500+ |
133+
134+
---
135+
136+
### Success Metrics
137+
138+
After publishing, track:
139+
140+
- Organic traffic (Google Search Console)
141+
- Keyword rankings for target terms
142+
- Free chapter downloads from blog CTAs
143+
- Time on page and scroll depth
144+
- Internal link clicks to book pages

.claude/content-calendar.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Content Calendar - Node.js Design Patterns Blog
2+
3+
**Publishing cadence:** 2 articles/month
4+
**Primary goal:** SEO traffic → Build authority → Drive book sales
5+
**Content approach:** Original content with light book excerpts
6+
7+
---
8+
9+
## Content Pillars
10+
11+
1. **Node.js Core APIs & Built-ins** - High-volume foundational topics
12+
2. **Modern Node.js Features** - New capabilities (22+, 23+) with early-mover SEO advantage
13+
3. **Node.js Patterns & Architecture** - Advanced patterns (light connection to book)
14+
15+
---
16+
17+
## 12-Month Calendar
18+
19+
### Month 1-2: Foundation
20+
21+
| # | Status | Article | Primary Keyword | Est. Volume | Pillar |
22+
| --- | ----------- | -------------------------------- | ------------------------------- | ----------- | --------- |
23+
| 1 | COMPLETE | **HTTP request in Node.js** | "node js http request" | 15,000+ | Core APIs |
24+
| 2 | NOT STARTED | Environment variables in Node.js | "node js environment variables" | 8,000+ | Core APIs |
25+
| 3 | NOT STARTED | Using Node.js built-in SQLite | "node js sqlite" | 5,000+ | Modern |
26+
| 4 | NOT STARTED | Writing a CLI with Node.js | "node js cli" | 4,000+ | Core APIs |
27+
28+
### Month 3-4: Core APIs
29+
30+
| # | Status | Article | Primary Keyword | Est. Volume | Pillar |
31+
| --- | ----------- | ------------------------------- | --------------------- | ----------- | --------- |
32+
| 5 | NOT STARTED | API server in Node.js (no deps) | "node js http server" | 6,000+ | Core APIs |
33+
| 6 | NOT STARTED | Hashing files with Node.js | "node js hash file" | 2,000+ | Core APIs |
34+
| 7 | NOT STARTED | Base64 encode/decode | "base64 node js" | 3,000+ | Core APIs |
35+
| 8 | NOT STARTED | Files and paths (node:path) | "node js path" | 4,000+ | Core APIs |
36+
37+
### Month 5-6: Patterns (Book-Adjacent)
38+
39+
| # | Status | Article | Primary Keyword | Est. Volume | Pillar |
40+
| --- | ----------- | --------------------------- | ----------------------- | ----------- | --------- |
41+
| 9 | NOT STARTED | Node.js Event Emitter guide | "node js event emitter" | 6,000+ | Patterns |
42+
| 10 | NOT STARTED | Interactive streams guide | "node js streams" | 10,000+ | Patterns |
43+
| 11 | NOT STARTED | How CommonJS works | "commonjs node js" | 3,000+ | Patterns |
44+
| 12 | NOT STARTED | Node.js console tips | "node js console" | 2,000+ | Core APIs |
45+
46+
### Month 7-8: Modern Node.js
47+
48+
| # | Status | Article | Primary Keyword | Est. Volume | Pillar |
49+
| --- | ----------- | ----------------------------- | ---------------------- | ----------- | --------- |
50+
| 13 | NOT STARTED | Type-stripping in Node.js | "node js typescript" | Growing | Modern |
51+
| 14 | NOT STARTED | Node.js built-in test runner | "node js test runner" | 3,000+ | Modern |
52+
| 15 | NOT STARTED | Import maps in Node.js | "node js import maps" | 1,000+ | Modern |
53+
| 16 | NOT STARTED | Encrypting files with Node.js | "node js encrypt file" | 1,500+ | Core APIs |
54+
55+
### Month 9-12: Advanced & Experimental
56+
57+
| # | Status | Article | Primary Keyword | Notes |
58+
| --- | ----------- | ------------------------------ | --------------- | --------------------- |
59+
| 17+ | NOT STARTED | Rust/Zig + Node.js integration | niche | Thought leadership |
60+
| 18+ | NOT STARTED | TBD based on analytics | TBD | Iterate based on data |
61+
62+
---
63+
64+
## Existing Content
65+
66+
| Topic | Coverage | Link |
67+
| ------------------------ | -------- | -------------------------------------------------------- |
68+
| Reading/writing files | COMPLETE | /blog/reading-writing-files-nodejs |
69+
| Stream consumers | COMPLETE | /blog/node-js-stream-consumer |
70+
| Async iterators | COMPLETE | /blog/javascript-async-iterators |
71+
| Race conditions | COMPLETE | /blog/node-js-race-conditions |
72+
| Checking Node.js version | COMPLETE | /blog/checking-node-js-version |
73+
| Installing Node.js | UPDATED | /blog/5-ways-to-install-node-js |
74+
| Docker development | COMPLETE | /blog/node-js-development-with-docker-and-docker-compose |
75+
76+
---
77+
78+
## Internal Linking Map
79+
80+
```
81+
HTTP Requests
82+
└── links to → API Server (no deps)
83+
└── links to → Reading/Writing Files (existing)
84+
85+
Environment Variables
86+
└── links to → CLI with parseArgs
87+
88+
Files & Paths
89+
└── links to → Reading/Writing Files (existing)
90+
└── links to → Hashing Files
91+
└── links to → Encrypting Files
92+
93+
Streams Guide
94+
└── links to → Stream Consumer (existing)
95+
└── links to → Async Iterators (existing)
96+
└── links to → Reading/Writing Files (existing)
97+
98+
Event Emitter
99+
└── links to → Streams Guide
100+
└── links to → Race Conditions (existing)
101+
102+
Installing Node.js
103+
└── links to → Checking Node.js version (existing)
104+
└── links to → Docker development (existing)
105+
```
106+
107+
---
108+
109+
## Quick Wins Checklist
110+
111+
- [x] Add FAQ schema support to blog layout
112+
- [x] Update "5 Ways to Install Node.js" with fnm, Volta, Docker
113+
- [x] Cross-link existing articles
114+
- [x] Add CTAs for free chapter download to all posts
115+
- [ ] Monitor keyword rankings for existing content
116+
- [ ] A/B test CTA placements
117+
118+
---
119+
120+
## Notes
121+
122+
- Focus on zero-dependency approaches using modern Node.js built-ins
123+
- Most competing articles still recommend axios/got first - opportunity to differentiate
124+
- New Node.js features (22+, 23+) have early-mover advantage
125+
- Light book excerpts where relevant (Ch 6 streams, Ch 10 testing)

.claude/settings.local.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@
33
"allow": [
44
"Bash(pnpm build:*)",
55
"Bash(pnpm exec astro build:*)",
6-
"Bash(pnpm install:*)"
6+
"Bash(pnpm install:*)",
7+
"Skill(marketing-skills:content-strategy)",
8+
"Bash(find:*)",
9+
"Bash(npm run build:*)",
10+
"WebSearch",
11+
"Bash(node fetch-get.js:*)",
12+
"Bash(node fetch-post.js)",
13+
"Bash(node http-get.js:*)",
14+
"Bash(node http-post.js:*)",
15+
"Bash(node:*)"
716
]
817
}
918
}

src/Layout.astro

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface Props {
2626
ogDescription?: string
2727
ogImage?: string
2828
canonical?: string
29-
additionalSchema?: object
29+
additionalSchema?: object | object[]
3030
noindex?: boolean
3131
}
3232
@@ -143,12 +143,20 @@ const {
143143
</script>
144144

145145
{
146-
additionalSchema && (
147-
<script
148-
type="application/ld+json"
149-
set:html={JSON.stringify(additionalSchema)}
150-
/>
151-
)
146+
additionalSchema &&
147+
(Array.isArray(additionalSchema) ? (
148+
additionalSchema.map((schema) => (
149+
<script
150+
type="application/ld+json"
151+
set:html={JSON.stringify(schema)}
152+
/>
153+
))
154+
) : (
155+
<script
156+
type="application/ld+json"
157+
set:html={JSON.stringify(additionalSchema)}
158+
/>
159+
))
152160
}
153161

154162
<script>

src/components/blog/BlogLayout.astro

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +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'
11+
import { generateBlogPostingSchema, generateFAQPageSchema } from '@lib/schema'
1212
import Breadcrumb from './Breadcrumb.astro'
1313
import BlogCard from './BlogCard.astro'
1414
@@ -31,7 +31,7 @@ type NestedCollectionHeadingsItem = CollectionEntryHeadingsItem & {
3131
}
3232
3333
const { post, authors, prevPost, nextPost } = Astro.props
34-
const { title, description, date, updatedAt } = post.data
34+
const { title, description, date, updatedAt, faq } = post.data
3535
3636
const formattedDate = formatDate(date)
3737
@@ -54,6 +54,10 @@ const blogPostingSchema = generateBlogPostingSchema({
5454
siteUrl: SITE_URL,
5555
})
5656
57+
// Generate FAQ schema if FAQ items are present
58+
const faqSchema = faq && faq.length > 0 ? generateFAQPageSchema(faq) : null
59+
const schemas = faqSchema ? [blogPostingSchema, faqSchema] : blogPostingSchema
60+
5761
// Groups ToC in a way that it's easier to render as nested lists
5862
const nestedToc: NestedCollectionHeadingsItem[] = []
5963
let lastNestedTocEntry: NestedCollectionHeadingsItem | null = null
@@ -78,7 +82,7 @@ if (post.rendered?.metadata?.headings) {
7882
ogTitle={title}
7983
ogDescription={description}
8084
canonical={`${SITE_URL}/blog/${post.id}/`}
81-
additionalSchema={blogPostingSchema}
85+
additionalSchema={schemas}
8286
>
8387
<main class="min-h-screen bg-base-100">
8488
<!-- Blog Header -->

src/content.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ const blog = defineCollection({
6363
description: z.string(),
6464
authors: z.array(reference('authors')),
6565
tags: z.array(z.string()),
66+
faq: z
67+
.array(
68+
z.object({
69+
question: z.string(),
70+
answer: z.string(),
71+
}),
72+
)
73+
.optional(),
6674
}),
6775
})
6876

0 commit comments

Comments
 (0)