# 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.*