SEO for Developers: The Parts That Actually Live in Your Code
For years, I treated SEO as someone else’s job. The marketing team handled keywords, and I shipped features. Then a client’s shiny newsite launched and quietly dropped off Google. Nothing was wrong with the content. The problem was entirely in the code.

That was when I learned that a surprising amount of SEO is just engineering. This post covers the parts you can fix directly, without touching a single keyword strategy.
Here’s the mental model I keep coming back to. A search engine sends a crawler to your site, and your job is to make three things easy for it:
- Finding your pages
- Understanding what each page is about
- Trusting that the page is fast and useful for real people
Every technique below serves one of those goals. When something feels like arbitrary SEO folklore, ask which of the three it supports. If you can’t answer, be skeptical.
Start with the tags that carry the most weight
The title tag
This is the most important on-page element you control. It’s the clickable headline in search results and the label on the browser tab.
<title>Best Running Shoes for Beginners (2026 Guide) | RunFast</title>
Every page needs its own title. If they’re all identical, you’re telling Google your pages are interchangeable. Aim for roughly 50 to 60 characters. Google actually truncates by pixel width rather than character count, but that range is a safe target. Put the important words first.
One caveat: Google sometimes rewrites titles it considers unhelpful (I read in an article, maybe wrong). A clear, accurate title makes that much less likely.
The classic developer mistake is hardcoding a title in a shared layout, so every route ships the same string. Make it dynamic per page.
The meta description
This is the gray text under the title in results. It doesn’t directly affect rankings, but a good one earns more clicks.
<meta name="description" content="A beginner's guide to picking your first pair of running shoes: cushioning, fit, and five budget picks under $80.">
Keep it around 150 to 160 characters, make it unique per page, and write it like a short pitch for the page. Google often generates its own snippet from your page content, especially when the description doesn’t match the search query. A well-written one still gives you the best chance of controlling what people see.
Heading hierarchy
Headings are the outline a crawler reads, much like you’d skim a document.
<h1>Running Shoes for Beginners</h1>
<h2>How to Choose Your First Pair</h2>
<h3>Cushioning</h3>
<h3>Fit and Sizing</h3>
<h2>Our Top Five Picks</h2>
One h1 per page is a sensible convention, but it isn't a hard rule. Google has said multiple h1s are fine. What matters more is a logical order without skipped levels. If you want smaller text, use CSS. A heading tag describes meaning, not font size.
Semantic HTML
Stop wrapping everything in div. Semantic tags tell crawlers and screen readers what each part of the page is for.
<header>...</header>
<nav>...</nav>
<main>
<article>...</article>
</main>
<footer>...</footer>
It’s a small change that pays off twice: once for search, once for accessibility.
Crawling is not indexing
Crawling is about access: can the robot visit and read the page? You control it with robots.txt.
Indexing is about visibility: can the page appear in search results? You control it with the noindex meta tag.
Many developers assume blocking a page in robots.txt hides it from Google. It doesn't.
Say you add Disallow: /secret/ to your robots.txt. Then another site links to yoursite.com/secret with the text "a great guide to running shoes." Google crawls that other site, sees the link, and learns two things: a page exists at that URL, and it's probably about running shoes.
Google can now index your page without ever reading it. It shows up as a bare link, often with the line “No information is available for this page.” Google is essentially saying, “I know this exists, but I wasn’t allowed in.”
The rule that follows: to keep a page out of search results, don’t block it in robots.txt. Let it be crawled and add noindex. The crawler has to get in to read that instruction.
robots.txt
A plain text file at the root of your domain:
User-agent: *
Disallow: /admin/
Disallow: /cart/
Allow: /
Sitemap: https://yoursite.com/sitemap.xml
User-agent: * applies the rules to all crawlers. Disallow blocks crawling of a path. Linking your sitemap here is good practice. And remember: this file controls crawling, not indexing.
The robots meta tag
This controls indexing page by page:
<meta name="robots" content="noindex">
<meta name="robots" content="nofollow">
<meta name="robots" content="noindex, nofollow">
Use noindex for thank-you pages, internal search results, staging environments, and printer-friendly duplicates. nofollow asks Google not to follow the page's links, though Google treats it as a hint rather than a strict rule.
Canonical URLs
One page often answers to many addresses:
https://site.com/product
https://site.com/product?ref=twitter
https://site.com/product?sort=price
http://site.com/product
To Google, that can look like four competing pages. A canonical tag names the preferred one:
<link rel="canonical" href="https://site.com/product">
Put one on every page, and a self-referencing canonical is perfectly normal. Just know it’s a strong hint, not a command. If your internal links, sitemap, and redirects all point elsewhere, Google may choose a different URL. Keep your signals consistent.
sitemap.xml
A sitemap hands the crawler a list of pages you want indexed:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://site.com/blog/running-shoes</loc>
<lastmod>2026-06-28</lastmod>
</url>
</urlset>
Only include URLs you actually want indexed: no noindex pages and no redirects. Google uses <lastmod> only when it's consistently accurate, so don't just stamp today's date on everything.
Generate the sitemap from your routes or database instead of maintaining it by hand, then submit it in Google Search Console.
Structured data with JSON-LD
Structured data describes your content in a machine-readable way, which can unlock rich results like star ratings, recipe cook times, and event dates. Google recommends JSON-LD:
Humans understand a page from context. If you see “4.6 ★ (218 reviews)” next to a shoe, you instantly know it’s a product rating. A crawler just sees text and numbers and has to guess. Schema removes the guessing by stating it explicitly: “this is a Product, its name is X, its price is 79.99 USD, its rating is 4.6 from 218 reviews.”
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Beginner Running Shoe",
"image": "https://site.com/shoe.jpg",
"offers": {
"@type": "Offer",
"price": "79.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "218"
}
}
</script>
A lot of older tutorials are out of date here. In 2023, Google limited FAQ rich results to well-known government and health websites, and it dropped HowTo rich results entirely. The markup is still valid, but for most sites it won’t produce those expandable dropdowns anymore.
The golden rule: structured data must match what users can actually see. Marking up a five-star rating that doesn’t appear on the page can earn you a manual action. Test everything with Google’s Rich Results Test before shipping.
Open Graph
Not strictly SEO, but it lives in your <head>, so it's yours:
<meta property="og:title" content="Best Running Shoes for Beginners">
<meta property="og:description" content="Our top five budget picks under $80.">
<meta property="og:image" content="https://site.com/preview.jpg">
<meta property="og:url" content="https://site.com/blog/running-shoes">
<meta property="og:type" content="article">
This controls the preview card when someone shares your link. Without it, you get a sad bare URL. Use an image around 1200 × 630 pixels.
Performance: where developers have the most leverage
Core Web Vitals
Google uses these three metrics as part of its page experience signals. To be honest, they won’t rescue weak content. Relevance still wins. But when pages are otherwise comparable, a fast, stable page has the edge, and your users will thank you regardless.

Improving LCP: compress images, use a CDN, remove render-blocking resources, and preload your hero image:
<link rel="preload" as="image" href="/hero.webp">
Fixing CLS: the usual culprit is images, ads, or embeds without reserved space. Give images explicit dimensions:
<img src="photo.jpg" alt="A runner" width="800" height="600">
Lowering INP: break up long JavaScript tasks, move heavy work off the main thread, and debounce expensive handlers.
Images
Images are usually the heaviest thing on a page:
<img
src="shoe.webp"
alt="Blue beginner running shoe"
width="800" height="600"
loading="lazy"
>
Use modern formats like WebP. Write honest alt text, since it’s how crawlers understand images and it’s essential for accessibility. Set width and height. Lazy-load images below the fold, but never lazy-load your hero image. That’s the one you want as early as possible.
Mobile
Google uses mobile-first indexing, meaning it mainly evaluates the mobile version of your site. Start with the viewport tag:
<meta name="viewport" content="width=device-width, initial-scale=1">
Without it, phones render your page at desktop width and shrink it. Beyond that, keep layouts responsive and tap targets big enough to hit comfortably.
URLs, links, and status codes
Clean URLs
Avoid: https://site.com/p?id=8231&cat=44
Better: https://site.com/blog/running-shoes-for-beginners
Use hyphens, keep URLs lowercase and short, and avoid changing them. When you must change one, redirect it properly.
Internal links
Anchor text tells crawlers what the destination is about:
<!-- Vague -->
Read more <a href="/blog/running-shoes">here</a>.
<!-- Descriptive -->
Check out our <a href="/blog/running-shoes">guide to running shoes for beginners</a>.
Also, use real <a href> links. A crawler won't click a <div onclick>.
JavaScript SEO
A traditional server-rendered page arrives with its content already in the HTML. A client-side React, Vue, or Angular app often arrives looking like this:
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
Google can render JavaScript, but rendering happens in a separate stage and isn’t guaranteed to go perfectly. Many other crawlers, and most social media preview bots, don’t run JavaScript at all. That’s why a pure SPA link often shows a blank preview card when shared.
The fix is to get real content into the initial HTML:
- Server-side rendering (SSR): the server renders the page before sending it. Next.js, Nuxt, and SvelteKit all support this.
- Static site generation (SSG): pages are pre-rendered at build time. It’s the fastest option and ideal for blogs, docs, and marketing pages.
My rule of thumb: if a page needs to rank or look good when shared, its content should exist in the HTML without JavaScript. Client-side rendering is fine for logged-in dashboards that never need indexing.
If this helped, feel free to share it with the developer :)