# Effective Technical SEO Strategies for Developer Blogs
URL: https://madhudadi.in/blog/posts/technical-seo-for-developer-blogs-proven-strategies
Published: 2026-06-19
Tags: Architecture, Next.js, Production, SEO
Read time: 16 min
Difficulty: intermediate
> hreflang strategy, dynamic OG image generation, sitemap structure, robots.txt for AI crawlers, canonical URLs, structured data validator results, and PageSpeed optimizations that moved the needle.# SEO That Actually Works for Dev Blogs
> "The best place to hide a dead body is page two of Google search results." — Anonymous search-marketing saying
Most SEO advice for dev blogs is generic: "write good content," "use keywords," "get backlinks." This post covers the technical SEO implementations that made a measurable difference for this site.
---
## hreflang Strategy
The site targets English-speaking audiences in India (en-IN) and globally (en). The hreflang implementation uses three variants:
```typescript
// Root layout (src/app/layout.tsx)
export const metadata: Metadata = {
alternates: {
languages: {
'en': '/blog',
'en-IN': '/blog',
'x-default': '/blog',
},
},
};
```
Every page that defines its own `alternates` includes the same three variants:
```typescript
// Per-page metadata
alternates: {
canonical: canonicalUrl,
languages: {
'en': canonicalUrl,
'en-IN': canonicalUrl,
'x-default': canonicalUrl,
},
},
```
The `en-IN` variant signals Google that the content is relevant to Indian users (who are the primary audience). The `x-default` variant catches all other language/region combinations. Without `x-default`, Google might not serve the page to users in unsupported regions.
---
## Dynamic OG Image Generation
The OG image endpoint at `/blog/api/og` generates a share card dynamically using Next.js edge rendering:
```typescript
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const slug = searchParams.get("slug");
const title = searchParams.get("title");
const post = slug ? await getPostBySlug(slug) : null;
const displayTitle = post?.title ?? title ?? "Madhu Dadi — AI, Python & Analytics Hub";
return new ImageResponse(
{displayTitle}
Madhu Dadi — AI, Python & Analytics Hub
,
{ width: 1200, height: 630 }
);
}
```
The endpoint generates:
- **Post pages** — `og?slug=post-slug` — renders the post title + site name
- **Series pages** — `og?series=series-slug` — renders the series title + part count
- **Tag pages** — `og?title=tag-name&tags=tag` — renders the tag name
- **Generic** — `og` — renders the default site title
---
## Sitemap Structure
The sitemap at `/blog/sitemap.xml` includes all published posts and series:
```typescript
export default async function sitemap(): Promise {
const posts = await postsApi.list({ limit: 1000, status: "published" });
const series = await seriesApi.list({ limit: 100 });
const postEntries = posts.items.map((post) => ({
url: `${SITE_URL}/blog/posts/${post.slug}`,
lastModified: post.updated_at ?? post.published_at,
changeFrequency: "monthly" as const,
priority: 0.8,
}));
const seriesEntries = series.items.map((s) => ({
url: `${SITE_URL}/blog/series/${s.slug}`,
lastModified: s.updated_at ?? s.created_at,
changeFrequency: "weekly" as const,
priority: 0.6,
}));
return [
{ url: `${SITE_URL}/blog`, changeFrequency: "daily", priority: 1.0 },
{ url: `${SITE_URL}/blog/posts`, changeFrequency: "daily", priority: 0.9 },
{ url: `${SITE_URL}/blog/series`, changeFrequency: "weekly", priority: 0.7 },
{ url: `${SITE_URL}/blog/tags`, changeFrequency: "weekly", priority: 0.5 },
{ url: `${SITE_URL}/blog/about`, changeFrequency: "monthly", priority: 0.3 },
...postEntries,
...seriesEntries,
];
}
```
The sitemap is regenerated daily by the background scheduler and submitted to Google via the Indexing API.
---
## robots.txt for AI Crawlers
The robots.txt explicitly welcomes AI crawlers while protecting user data:
```
User-agent: *
Allow: /blog/api/og
Allow: /blog
Disallow: /blog/admin
Disallow: /blog/profile
Disallow: /blog/bookmarks
Disallow: /blog/api
Disallow: /blog/auth
```
The OG image allow must come before the `/blog/api` disallow. Social media crawlers (LinkedIn, Facebook, Twitter) check robots.txt before fetching the OG image — if `/blog/api` is disallowed without an explicit allow for `/blog/api/og`, the share card won't render.
For AI crawlers, the permissions are broader:
```
User-agent: GPTBot
User-agent: ClaudeBot
User-agent: PerplexityBot
...
Allow: /blog
Allow: /blog/posts
Allow: /blog/series
Allow: /blog/ask
Allow: /blog/api/og
Allow: /blog/llms.txt
Allow: /blog/ai-profile.json
Disallow: /blog/admin
```
---
## Canonical URLs
Every page defines a canonical URL to prevent duplicate content issues:
```typescript
const canonicalUrl = `${SITE_URL}/blog/posts/${slug}`;
return {
alternates: {
canonical: canonicalUrl,
languages: { ... },
},
};
```
The canonical URL is always the `https://` version with the correct path. No trailing slash, no `www` prefix, no query parameters (except for paginated list pages).
---
## Structured Data Validation
Every post page is validated against Google's Rich Results Test. The five schema blocks (TechArticle, FAQPage, HowTo, BreadcrumbList, Course) are tested individually:
```typescript
// Validated output (TechArticle)
{
"@type": "TechArticle",
"headline": "Building a RAG Chat System From Zero",
"description": "How the Ask AI page works...",
"teaches": ["Embedding Pipeline", "HNSW Index", "Hybrid Search"],
"educationalLevel": "Advanced",
"timeRequired": "PT20M",
"wordCount": 3200,
}
```
Key validation rules:
- `headline` must be the clean title without brand suffixes
- `description` must be under 160 characters
- `teaches` should contain 3-10 specific topics (not generic tags)
- `educationalLevel` must be capitalized (Beginner, Intermediate, Advanced)
- `@type` must be a single type, not an array
---
## PageSpeed Optimizations
Three optimizations that improved Lighthouse scores:
### 1. Font Display Swap
```typescript
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap",
preload: true,
});
```
`display: "swap"` ensures text is visible immediately with a fallback font while the custom font loads. Without this, Lighthouse flags "Ensure text remains visible during webfont load."
### 2. Preconnect to External Origins
```html
```
Preconnecting to Google Tag Manager and Google Analytics shaves ~200ms off the initial connection time.
### 3. Optimized Image Loading
```typescript
images: {
remotePatterns: [
{ protocol: "https", hostname: "madhudadi.in" },
],
},
```
All images are served with Next.js Image Optimization, which automatically generates WebP versions, sets appropriate sizes, and adds lazy loading.
---
## What These Changes Achieved
| Optimization | Before | After | Impact |
|-------------|--------|-------|--------|
| hreflang | None | en + en-IN + x-default | Better regional targeting |
| OG images | Missing on 3 pages | All pages | Share card renders everywhere |
| Structured data | 2 schema types | 5 schema types | Rich results eligibility |
| robots.txt | Blocked OG endpoint | Explicit allow | Share card on social media |
| Canonical URLs | Inconsistent | Uniform | No duplicate content issues |
| PageSpeed | ~65 mobile | ~92 mobile | Better ranking signal |
---
## What's Next
The final post in this series — the honest retrospective: what I'd do differently, what I'd do the same, the numbers (build time, cost, traffic), and what broke in production.
---
*Built with Next.js 16, structured data, and zero third-party SEO plugins.*