DEV Community

Cover image for A Practical SEO Audit Checklist for Developers
Jay Barker
Jay Barker

Posted on

A Practical SEO Audit Checklist for Developers

Sooner or later, someone from marketing sends you an SEO report. It's a 40-page PDF full of red warnings, half of them duplicates, and none of them explain how to reproduce the problem. "Canonical issues detected." Which pages? Detected how? You close the PDF and go back to your sprint.

I've been on the receiving end of that report more times than I'd like. So this is the SEO audit checklist I wish I'd had: every item comes with a way to verify it yourself (usually a curl command or a DevTools panel) and a way to fix it.

Here's the thing most SEO checklists skip. The problems that hurt rankings the most are usually code problems. A leftover noindex header. A redirect chain from a framework migration. A hero image that takes four seconds to paint. Marketers can spot the symptoms, but developers are the ones who can find the cause and ship the fix.

Let's go through it phase by phase.

What an SEO Audit Is (and Which Parts Developers Own)

The three layers of an audit

An SEO audit is a structured review of everything that affects how a site performs in search. It usually covers three layers:

  • Technical SEO: can search engines crawl, render and index your pages, and do those pages load fast?
  • On-page SEO: are titles, headings, content and markup clear about what each page is about?
  • Off-page SEO: how many trusted sites link to you, and what does your reputation look like across the web?

Where developers fit in

Off-page work and most content decisions belong to the marketing side. But crawling, indexing, rendering, performance, markup and server configuration all live in your codebase. They also tend to break silently. Nobody files a bug when Googlebot stops indexing your product pages. Traffic just drifts down for three months until someone notices.

That's the layer this checklist focuses on. If you get it right, the content team's work actually has a chance to rank.

The Free Tool Stack You Need

You don't need a paid SEO suite for any of this. Here's what I use:

  • Google Search Console. The source of truth for how Google sees your site. The Pages report shows what's indexed and why the rest isn't, and the URL Inspection tool shows the HTML Googlebot actually rendered.
  • Lighthouse. Built into Chrome DevTools, and also available as a CLI for scripting and CI.
  • PageSpeed Insights. Runs Lighthouse, but more importantly shows field data from real Chrome users.
  • A crawler. Screaming Frog's free version crawls up to 500 URLs, which covers most small and mid-sized sites.
  • curl and DevTools. For headers, status codes, redirects and comparing raw HTML with the rendered DOM.
  • Google's Rich Results Test. For validating structured data.
  • A quick baseline report. A free SEO audit tool gives you a scored snapshot of a page across on-page, speed and security checks in about a minute. It's handy for a first pass and for before-and-after comparisons.

Phase 0: Get a Baseline Before You Touch Anything

Before fixing anything, record where you're starting from. Otherwise you'll have no way to prove your changes worked.

The key idea is to audit templates, not pages. Most sites are a handful of templates rendering thousands of URLs: homepage, listing or category page, detail page, blog post, maybe a search results page. A bug in the product template is a bug on every product page, and one fix solves all of them.

Pick one representative URL per template, then save a Lighthouse report for each so you can diff the numbers later:

npx lighthouse https://example.com/products/sample-item \
  --output=json \
  --output-path=./audit/baseline-product.json \
  --chrome-flags="--headless"
Enter fullscreen mode Exit fullscreen mode

Run each template through your baseline tool as well and note the scores in a spreadsheet or the audit ticket. Ten minutes here saves a lot of arguing later about whether a change helped.

Phase 1: Crawlability and Indexation

This is the most important phase. If search engines can't crawl or index a page, nothing else on this list matters for that page.

robots.txt

Check that robots.txt isn't blocking anything important. The two classic mistakes are a Disallow: / copied over from staging, and rules that block CSS or JS directories. Google needs those files to render the page properly.

curl -s https://example.com/robots.txt
Enter fullscreen mode Exit fullscreen mode

Read every Disallow line and ask whether it should really be there. Also remember that robots.txt controls crawling, not indexing. A blocked URL can still appear in results if other sites link to it, so it's the wrong tool for keeping a page out of Google. Google's robots.txt documentation covers the syntax and edge cases well.

XML sitemap

Your sitemap should list only canonical, indexable URLs that return a 200. No redirects, no 404s, no noindexed pages. Reference it in robots.txt with a Sitemap: line and submit it in Search Console.

Here's a quick way to spot bad entries in a simple, single-file sitemap:

curl -s https://example.com/sitemap.xml \
  | grep -oP '(?<=<loc>)[^<]+' \
  | while read -r url; do
      echo "$(curl -s -o /dev/null -w '%{http_code}' "$url") $url"
    done | grep -v '^200'
Enter fullscreen mode Exit fullscreen mode

Anything this prints needs to be fixed or removed from the sitemap. One more note: Google ignores priority and changefreq, but it does use lastmod when the value is accurate. If your CMS sets lastmod to today's date on every build, fix that too.

noindex in meta tags and headers

There are two ways to tell search engines not to index a page, and you need to check both:

# HTTP header
curl -sI https://example.com/some-page | grep -i x-robots-tag

# Meta tag in the HTML
curl -s https://example.com/some-page | grep -i 'name="robots"'
Enter fullscreen mode Exit fullscreen mode

The header version is the sneaky one. It's often set at the server or CDN level for staging environments and then accidentally carried into production. It won't show up when you inspect the page source, so marketers almost never catch it.

Canonical tags

Every indexable page should have a self-referencing canonical tag with an absolute URL. Rules worth checking:

  • Exactly one canonical per page.
  • It points to a URL that returns a 200, not a redirect or a 404.
  • Paginated pages and filtered listing pages don't all point to page one unless that's really what you want.
  • The canonical in the HTML matches any canonical sent in an HTTP Link header.

Remember that a canonical is a hint, not a directive. If your canonicals contradict your internal links and sitemap, Google may pick its own.

Status codes and redirect chains

Pages that don't exist should return a real 404 (or 410), not a 200 with "Page not found" in the body. Google calls those soft 404s, and they waste crawl effort. Test it directly:

curl -s -o /dev/null -w '%{http_code}\n' https://example.com/this-page-does-not-exist-123
Enter fullscreen mode Exit fullscreen mode

Next, check redirects. Permanent moves should use a 301 (or 308), and each redirect should take one hop to reach its final URL:

curl -sIL http://example.com/old-page | grep -iE '^(HTTP|location)'
Enter fullscreen mode Exit fullscreen mode

If you see http to https to www to a trailing-slash version, that's a three-hop chain. Collapse it into a single redirect straight to the final URL. Chains build up quietly after every migration, so run this on old URLs that still get backlinks.

JavaScript rendering

If your site is a single-page app, this check matters more than any other. Compare what the server sends with what the browser renders:

curl -s https://example.com/products/sample-item | grep -i "sample item name"
Enter fullscreen mode Exit fullscreen mode

If your main content and internal links aren't in the server response, search engines have to execute your JavaScript to see them. Google can render JS, but rendering can be delayed, and other crawlers often won't do it at all. Use the URL Inspection tool in Search Console to view the rendered HTML Google actually captured.

For content-heavy pages, server-side rendering or static generation is the safest choice. Also make sure navigation uses real <a href> links. Click handlers on divs don't count as links to a crawler.

Phase 2: Site Architecture and Internal Linking

Click depth

Important pages should be reachable within three clicks of the homepage. Your crawler will report click depth for every URL. If key product or service pages sit five or six levels deep behind pagination, add links from category pages, navigation or related-content blocks.

Orphan pages

An orphan page is one with no internal links pointing to it. It might be in your sitemap, but if nothing on the site links to it, Google treats it as unimportant. To find orphans, export the URLs from your crawl and compare them with the URLs in your sitemap. Anything in the sitemap that the crawler never reached needs a link from somewhere relevant.

URL structure

Keep URLs lowercase, use hyphens between words, and leave session IDs and tracking parameters out of internal links. Pick one trailing-slash convention and enforce it with a redirect. /blog/post and /blog/post/ are two different URLs as far as a crawler is concerned.

Breadcrumbs and a custom 404

Breadcrumbs give users a way back up the hierarchy and give search engines a clear map of your structure, especially when paired with BreadcrumbList markup. Your custom 404 page should help people find their way (search box, popular links) while still returning a 404 status code. Check the status code with the curl command from Phase 1.

Phase 3: Performance and Core Web Vitals

Know the thresholds

Google measures page experience with three Core Web Vitals, assessed at the 75th percentile of real visits:

Metric What it measures Good
Largest Contentful Paint (LCP) How fast the main content appears 2.5 s or less
Interaction to Next Paint (INP) How quickly the page responds to input 200 ms or less
Cumulative Layout Shift (CLS) How much the layout jumps while loading 0.1 or less

The Core Web Vitals thresholds on web.dev explain how each one is measured if you want the details.

Lab data vs field data

Lighthouse gives you lab data: a single simulated load on a throttled device. It's great for debugging because it's repeatable. But Google uses field data from real Chrome users (the CrUX dataset), which you'll find at the top of PageSpeed Insights and in the Core Web Vitals report in Search Console.

So don't panic over one bad Lighthouse run, and don't celebrate one good one either. Use the field data to decide what to fix and lab data to work out how.

Fix LCP

The LCP element is usually a hero image or a large heading. Common fixes:

  • Don't lazy-load the LCP image. loading="lazy" above the fold is one of the most common LCP bugs I see.
  • Tell the browser it's important with fetchpriority="high", or preload it.
  • Serve modern formats like WebP or AVIF, at the size actually displayed.
  • Cut server response time (TTFB) with caching, because nothing can paint before the HTML arrives.
<img src="/img/hero.avif" alt="Product dashboard overview"
     width="1200" height="600" fetchpriority="high">
Enter fullscreen mode Exit fullscreen mode

Fix INP

INP is usually a JavaScript problem. Break up long tasks so the main thread can respond to input, defer non-critical scripts, and take a hard look at third-party tags. Chat widgets, A/B testing tools and stacked analytics scripts are frequent offenders. The Performance panel in DevTools will show you exactly which tasks block interactions.

Fix CLS

Always set width and height (or aspect-ratio) on images and video so the browser reserves space before they load. Reserve space for ads, embeds and cookie banners. If web fonts cause text to reflow, look at font-display and preloading your main font file.

Delivery basics

These are quick server-side wins, and you can verify all of them from the terminal:

# Compression (look for br or gzip)
curl -sI -H 'Accept-Encoding: br,gzip' https://example.com | grep -i content-encoding

# Caching on static assets
curl -sI https://example.com/assets/app.js | grep -i cache-control

# HTTP/2 support
curl -sI --http2 https://example.com | head -1
Enter fullscreen mode Exit fullscreen mode

Static assets with hashed filenames can use long cache lifetimes. Add a CDN if your users are spread out geographically, and make sure CSS and JS are minified in production builds.

Phase 4: On-Page Technical Elements

Title tags and meta descriptions per template

Check that every template generates a unique, descriptive title and meta description from real page data. The common bugs are duplicates on paginated pages, filtered URLs that all inherit the category title, and empty fallbacks when a field is missing in the CMS. Keep titles to roughly 60 characters so they don't get cut off in results. A meta tag analyzer will show you how a page's tags will look in search and flag length problems.

Heading hierarchy

One H1 per page, describing the page's main topic. Use H2 and H3 for structure, not for styling. If a designer wants smaller bold text, that's a CSS class, not an <h4>.

lang, viewport and charset

These are tiny, and templates still get them wrong:

<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
Enter fullscreen mode Exit fullscreen mode

Image alt text

Make alt a required prop in your image component, so it can't be skipped. Describe what the image shows. Purely decorative images should use an empty alt="" so screen readers skip them.

Open Graph and Twitter cards

These don't affect rankings directly, but they control how your links look when shared on social platforms and in chat apps. At minimum, set og:title, og:description, og:image and og:url per page.

hreflang, if you're multilingual

If you serve the same content in multiple languages or regions, each version needs hreflang tags pointing to every other version, including itself. The tags must be reciprocal. If page A points to page B, page B must point back to A, or Google ignores the pair.

Phase 5: Security and Server Configuration

HTTPS everywhere, plus HSTS

Every HTTP URL should 301 to its HTTPS equivalent. Once that's solid, add an HSTS header so browsers go straight to HTTPS:

curl -sI https://example.com | grep -i strict-transport-security
Enter fullscreen mode Exit fullscreen mode

Mixed content

An HTTPS page that loads images, scripts or styles over HTTP triggers mixed-content warnings, and browsers may block those resources. Open the DevTools console on your main templates and look for warnings. Hardcoded http:// URLs in old CMS content are the usual culprit.

One canonical host

http://example.com, http://www.example.com, https://example.com and https://www.example.com should all end up at the same URL with a single 301. Run the redirect check from Phase 1 against each variant. Duplicate hosts split your signals and waste crawl effort.

Leaky headers and directory listing

Hide server version details in headers like Server and X-Powered-By, and turn off directory browsing. These are security issues first, but site audit tools flag them, and they're five-minute fixes.

Phase 6: Structured Data

Structured data tells search engines what your content is, not just what it says. For most sites, the useful types are:

  • Organization (or LocalBusiness for businesses with a physical location)
  • BreadcrumbList
  • Article for blog posts
  • Product for e-commerce

Use JSON-LD, and generate it from the same data that renders the visible content so the two never drift apart:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Example Co",
  "url": "https://example.com",
  "logo": "https://example.com/logo.png"
}
</script>
Enter fullscreen mode Exit fullscreen mode

Validate every template with the Rich Results Test. One expectation to set with your team: Google now shows FAQ rich results only for a small set of authoritative sites, such as government and health sites. FAQ markup is still valid, but don't promise anyone those dropdowns in search results.

Phase 7: Prioritize, Ship and Prevent Regressions

Turn findings into tickets

An audit is only useful if it turns into shipped work. Score each finding on two axes, impact and effort, and work through them in this order:

  1. Indexing blockers. Stray noindex, robots.txt blocks, broken canonicals, rendering failures.
  2. Template-level fixes. One change, hundreds of pages fixed.
  3. Performance work on the templates with the worst field data.
  4. Single-page fixes and minor warnings.

If you're wondering how to improve SEO score in a way that actually matters, this is it: fix the issues with the biggest blast radius first, not whichever warning is easiest to clear.

Catch regressions in CI

The best audit is the one you never have to repeat by hand. Lighthouse CI can fail a build when performance or SEO scores drop below a budget:

{
  "ci": {
    "collect": {
      "url": ["http://localhost:3000/", "http://localhost:3000/blog/sample-post"]
    },
    "assert": {
      "assertions": {
        "categories:seo": ["error", { "minScore": 0.95 }],
        "categories:performance": ["warn", { "minScore": 0.9 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "largest-contentful-paint": ["warn", { "maxNumericValue": 2500 }]
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Save that as lighthouserc.json and run npx @lhci/cli autorun in your pipeline. I'd also add a post-deploy smoke test that fails loudly if production ever serves a noindex:

URL="https://example.com/"
if curl -sI "$URL" | grep -qi 'x-robots-tag:.*noindex' \
   || curl -s "$URL" | grep -qiE 'name="robots"[^>]*noindex'; then
  echo "noindex found on production: $URL" && exit 1
fi
Enter fullscreen mode Exit fullscreen mode

It's a few lines of shell, and it guards against one of the most expensive SEO bugs there is.

The Copy-Paste Checklist

Drop this into a GitHub issue or PR description and tick items off as you go:

### Phase 0: Baseline
- [ ] One URL per template selected
- [ ] Lighthouse JSON and baseline scores saved

### Phase 1: Crawl and index
- [ ] robots.txt blocks nothing important (CSS/JS included)
- [ ] Sitemap lists only 200, canonical, indexable URLs
- [ ] No stray noindex in meta tags or X-Robots-Tag headers
- [ ] Self-referencing absolute canonicals on indexable pages
- [ ] Real 404s, no soft 404s
- [ ] Redirects are single-hop 301s
- [ ] Main content and links present in server HTML

### Phase 2: Architecture
- [ ] Key pages within 3 clicks
- [ ] No orphan pages
- [ ] Consistent, lowercase, hyphenated URLs

### Phase 3: Performance
- [ ] LCP, INP and CLS pass in field data
- [ ] LCP image not lazy-loaded
- [ ] Compression, caching and HTTP/2 enabled

### Phase 4: On-page
- [ ] Unique titles and descriptions per template
- [ ] One H1, logical heading order
- [ ] lang, charset and viewport set
- [ ] Alt text required in image component

### Phase 5: Security
- [ ] HTTPS with HSTS, no mixed content
- [ ] One canonical host

### Phase 6: Structured data
- [ ] JSON-LD validates in Rich Results Test

### Phase 7: Prevent regressions
- [ ] Lighthouse CI budgets in pipeline
- [ ] Post-deploy noindex check
Enter fullscreen mode Exit fullscreen mode

If a check fails and you're not sure why it matters or how to fix it, this full reference of 70 SEO checks, explained covers each one in plain terms, with a fix for each failing result.

FAQs

What should an SEO audit checklist include?

At minimum: crawlability and indexation (robots.txt, sitemaps, noindex, canonicals, status codes), site architecture, Core Web Vitals and performance, on-page elements like titles and headings, HTTPS and server configuration, and structured data. A complete marketing audit also covers content quality and backlinks.

How often should you run a technical SEO audit?

Do a full audit once or twice a year, plus a targeted one after any major release, redesign, framework change or migration. Between audits, automated checks in CI and a weekly look at Search Console's Pages and Core Web Vitals reports will catch most regressions early.

How long does an SEO audit take?

For a small site with a few templates, a developer can work through this checklist in half a day to a day. Large sites with many templates, multiple languages or JavaScript rendering issues can take a week or more, mostly because of investigation time rather than the checks themselves.

Can I do an SEO audit myself for free?

Yes. Google Search Console, Lighthouse, PageSpeed Insights, the Rich Results Test, the free version of Screaming Frog and curl cover everything in this checklist. Paid tools mainly add scale, scheduling and backlink data, which you won't need for a developer-focused technical audit.

What's the difference between a technical SEO audit and an on-page audit?

A technical audit checks whether search engines can crawl, render, index and load your pages efficiently. An on-page audit checks whether each page's content and markup clearly match what people search for. Developers usually own the technical side, and content teams own most of the on-page side.

Does passing every check mean my site will rank?

No. Passing every check removes the obstacles, but rankings still depend on content that matches search intent and on links from trusted sites. Think of a clean technical audit as a requirement for ranking, not a guarantee of it.

Wrapping Up

Audit templates instead of pages, fix indexing blockers before anything else, and put the important checks into CI so the same bug never ships twice. Do that, and SEO stops being a scary PDF from marketing and becomes just another part of your test suite.

Top comments (0)