Your audit report goes to a new client on Thursday. On Friday their developer answers in one line: the report says hundreds of pages have duplicate titles, and the CMS export shows a handful. You reopen the crawl and find that you were both right. Your crawler followed filter and tracking-parameter URLs, while the CMS export lists only the pages it publishes. Nothing in the report said what was crawled, with which settings, or on what day. The next call is now about the report instead of the website, and the findings that matter sit underneath the argument.
An SEO audit report is a set of claims about a site. Each one has to survive a developer, a skeptical owner and your own memory of what you did three weeks ago. This guide covers what belongs in the report, how to record evidence so that any finding can be re-checked quickly, how to rank findings without a vendor score, and what to cut so the important items stay visible.
Disclosure: I build Rankfinity, a rank tracker with a site audit, so I have an interest in how audit reports get written. Every rule or threshold below links to the page that publishes it, and I read each of those pages on 2026-10-08, so you can check the claims without trusting me.
Key takeaways. Treat every finding as a claim with a record behind it: what was measured, with which settings, on which day, and how to reproduce it. Open the report with a scope block. Label each finding with the layer it comes from, because what a crawler saw, what Google reports and what real visitors experienced are different sources. Rank findings by reach, consequence and effort instead of a vendor health score, and cut any finding you cannot describe as “fixing this changes X for Y URLs”.
What the report is for
The audit is the investigation. The report is what the client acts on, and it has three jobs: say what is wrong, show how you know, and decide what happens first. Anything that does none of those is decoration, and decoration is where readers stop reading.
It helps to separate this artifact from its neighbors. A monthly performance report tracks outcomes over time. A proposal sells work. An audit report is a point-in-time diagnosis with a baseline attached, which is why the section list in a typical website audit report template matters less than whether each section carries evidence a stranger could check. The crawl behind the report can come from any tool. Where I need an example of what a tool records, I use the Rankfinity site audit, because its documentation is public and I can link each claim to it.
Outcomes over time
Tracks results against a baseline.
Sells work
Describes what you would do.
A point in time diagnosis
Claims about a site, each with a record behind it, and a baseline attached.
Each claim has to survive a developer, a skeptical owner and your own memory of what you did three weeks ago.
Start with a scope block
The scope block is the part most reports skip, and it would have saved the Friday call. It states the conditions under which every number in the report was produced. Without it, a count of errors has no meaning, because the same site crawled with different settings returns different totals.
| Field | Why it changes the result | What to write |
|---|---|---|
| Crawl dates | Content and server behavior change between runs | The day the crawl finished, plus the tool and its version |
| User agent | Some sites answer different agents differently | The agent string the crawler sent |
| JavaScript rendering | Text added by scripts is invisible to a crawler that reads only HTML | On or off, and the pages where it matters |
| Start URLs and rules | Include and exclude patterns decide which URLs exist for the crawl | The seed URLs and every pattern used |
| Parameter handling | Filter and tracking parameters can multiply URLs | Whether parameter URLs were followed or ignored |
| robots.txt | A crawler that obeys it skips pages a person can still open | Obeyed or ignored, and which sections it blocked |
| Limits | A crawl that stops at its page cap is a sample, not the site | The cap, and whether the crawl reached it |
| Search Console property | A domain property and a URL-prefix property cover different URLs | The property type and the date range used for clicks |
| Other sources | Analytics, field speed data and server logs each have gaps | Which were used and which were not available |
Two rows deserve a closer look. A crawl that stops at its limit is a sample, so a report that says “hundreds of issues” without mentioning the cap understates the site. The Rankfinity site audit marks a crawl that hit its size or time limit as “Stopped early” (site audit documentation, checked 2026-10-08), and whatever tool you use should tell you the same thing. The second row is JavaScript. The same documentation warns that pages which show text only after scripts run “may look nearly empty” to its crawl. That is a limitation worth declaring, and the scope block is where you declare it.
Add one row for AI crawlers to the scope block: which search bots and training bots your robots.txt allows or blocks, and the date you last checked. The bot-by-bot list, with the role of each one and what a block does, is in Is Your Site Ready for AI Agents? The 2026 Checklist (20 Checks).
Label every finding with its layer
Most disputed findings are disputed because they mix sources. A crawler shows what a bot can reach and what each page says about itself. Google reports what it did with a URL. Field data shows how real visitors experienced a page, while lab data shows how one simulated load behaved. Outcomes show what changed for the business. Each layer answers a different question, and the report should say which one a finding rests on.
| Layer | Where it comes from | What it can tell you | Typical mistake |
|---|---|---|---|
| Crawl | Your crawler’s requests and the parsed HTML | What a bot can reach, what each URL returns and what its tags say | Calling a page indexed because the crawl found no noindex tag |
| Google’s index | Search Console Page indexing report and URL Inspection | What Google did with the URL when it last looked | Treating a crawl-only finding as proof that Google acted on it |
| Field data | Real-user measurements such as the Chrome User Experience Report | How the page behaved for actual visitors | Describing visitors with lab numbers |
| Lab data | A simulated load, such as Lighthouse | How the page behaved on one device and one network profile | Reporting a lab score as a Core Web Vitals verdict |
| Outcomes | Search Console clicks, analytics, CRM | What changed for the business | Leaving this layer out, so no finding connects to a result |
Take a service page that carries a noindex tag by mistake. The crawl shows the tag. The Page indexing report in Search Console, which “shows the Google indexing status of all URLs that Google knows about in your property” (Search Console Help, checked 2026-10-08), lists the URL as excluded, and its explanation says that when Google tried to index the page it “encountered a ‘noindex’ directive and therefore did not index it”. The URL’s clicks in the date range then show what the exclusion cost. Three layers tell one story, so the finding gets a crawl row, a Google row and an outcome row, and nobody has to take your word for it.
The split matters because a crawler can only report eligibility: a 200 response, no noindex, a canonical that points at the page itself. It cannot say the page is indexed. The status names in Google’s report show how fine the differences are. “Crawled – currently not indexed” is explained as “The page was crawled by Google but not indexed.” while “Discovered – currently not indexed” means “The page was found by Google, but not crawled yet.” They call for different fixes, and a crawl export cannot tell them apart.
- Crawl rowThe service page carries a noindex tag.
- Google rowSearch Console lists the URL as excluded because Google encountered a noindex directive.
- Outcome rowThe URL’s clicks over the date range show what the exclusion cost.
Three places where layers get confused
Indexed version versus live test
When a client says a page is fixed, ask which version they looked at. The URL Inspection tool reports on the “most recently indexed version of a page, not the live version on the web”, and its live test carries its own warning: “A positive result is not a guarantee that it will appear in Search results” (URL Inspection tool help, checked 2026-10-08). A fix can be live and still not be reflected in the indexed version for a while.
Blocked versus not indexed
These are separate states. Google’s documentation says a robots.txt file “is not a mechanism for keeping a web page out of Google” and points to noindex or password protection for that job (Google Search Central, checked 2026-10-08). If your report calls a page blocked, say what the evidence shows: the robots.txt rule that matched, or the index status Google reports. Do not use one word for both.
Lab data versus field data
Speed findings are where layers collide most often. Google’s guidance for Core Web Vitals is to aim for Largest Contentful Paint within the first 2.5 seconds, Interaction to Next Paint under 200 milliseconds and Cumulative Layout Shift under 0.1 (Core Web Vitals in Google Search, checked 2026-10-08). PageSpeed Insights then separates two kinds of data. Lab data is “useful for debugging issues” but “may not capture real-world bottlenecks”, while field data is “useful for capturing true, real-world user experience” (PageSpeed Insights documentation, checked 2026-10-08). A speed finding should therefore name its data source. A lab-only failure is a lead for the developer. Only field data is a statement about visitors.
Ask which version
A fix can be live and not yet reflected in the indexed version.
Say what the evidence shows
The robots.txt rule that matched, or the index status Google reports. Never one word for both.
Name the data source
A lab only failure is a lead for the developer. Only field data speaks for visitors.
Record each finding the same way
Once the scope is stated, every finding gets one row in the same shape. The shape is what makes a report auditable, and it doubles as the audit report template many people search for. These are the fields I would want to see before believing a finding.
| Field | What to write | Why it protects you |
|---|---|---|
| ID | A stable code for the finding | The client can cite it in a ticket and everyone knows which row it is |
| Finding | One observable sentence, no adjectives | “Product pages return a 302 to the trailing-slash URL” beats “bad redirects” |
| Layer | Crawl, Google, field, lab or outcome | Tells the reader how much weight the finding carries alone |
| Affected URLs | The count, the number of URLs checked, and a link to the export | A count without the number checked is not a measurement |
| Evidence | The exact export, report name and column | Shows the claim came from a record, not from memory |
| Reproduce | The shortest steps to see it yourself | A developer who can reproduce a finding in a minute rarely disputes it |
| Consequence | What changes for crawling, indexing, rendering or visitors | Separates a defect from a preference |
| Priority | Reach, consequence and effort, each stated | Makes the ordering arguable on its own terms |
| Owner and status | Who acts, and whether it is open, in progress or verified | Turns the report into a work list |
The affected-versus-checked pair matters more than it looks. The Rankfinity documentation makes the same point: its error, warning and notice totals “are counts of failed checks, not of pages”, and each issue page shows pages affected next to pages checked (Rankfinity documentation, checked 2026-10-08). A report that quotes the first number without the second invites the wrong reading.
Add a “passes when” line to the top findings too. Rankfinity’s issue pages carry one, and the idea works anywhere: agree on the success condition before the work starts, and whether something is fixed stops being a matter of opinion. A re-crawl of the same URLs with the same settings then answers it.
“Bad redirects”
Nothing to reproduce, nothing to verify.
“Product pages return a 302 to the trailing-slash URL”
One sentence, no adjectives.
One finding, filled in (illustrative, not a real site)
- ID: F-03
- Finding: Product pages return a 302 to the trailing-slash URL
- Layer: Crawl
- Affected URLs: the count, the number of URLs checked, and a link to the export
- Reproduce: request any product URL without the slash and read the status code
- Passes when: the same crawl returns 301 or no redirect on those URLs
Rank findings without a health score
Composite scores are convenient and hard to defend. The weights belong to the vendor, the checks included change over time, and two tools give the same site two different numbers. The Rankfinity documentation says a health score can drop when nothing on the site changed, because “a new check may have been added” or “the crawl found pages it did not reach before” (same documentation page). That is honest behavior from a tool, and it is also the reason the number should not headline your report. Show it if the client expects it, define it in a footnote, and lead with affected counts.
Three factors are enough to order findings, and each can be stated with evidence. Reach is how many URLs the finding touches and how much search traffic they receive. Consequence is what changes if it is left alone. Effort is who has to do what. The consequence tier does most of the sorting.
| Tier | Test | Typical findings |
|---|---|---|
| 1. Blocks discovery or indexing | Can Google fetch and index the page that should rank? | Noindex on a page meant to rank, server errors on key URLs, redirect loops, a canonical pointing at a different page |
| 2. Weakens how the page is understood or shown | Does the page say what it is about, once? | Missing or duplicate titles on money pages, orphaned pages with no internal links, duplicated copy |
| 3. Housekeeping | Would a visitor or Google notice? | Oversized images, redirect chains on low-traffic URLs, cosmetic markup warnings |
A tier two finding on the page that brings in most of a client’s leads can outrank a tier one finding on a retired section, which is why reach is its own field. In Rankfinity the Pages tab shows clicks and sessions beside each crawled URL when Search Console or Google Analytics is connected (crawl report documentation), and the same table can be built by joining a crawl export to a Search Console export on URL.
Duplicate titles on the page that brings in most leads
Reach: high. Consequence: weakens how the page is shown. Effort: one template change. Goes first.
Noindex on a retired section
Reach: almost none. Consequence: blocks indexing of pages nobody needs. Effort: low. Goes later.
This is why tiers alone are not enough. The consequence tier does most of the sorting, and reach decides the order inside and across tiers.
What to leave out
A report earns trust by what it declines to say. Cut every finding that fails the sentence test: “fixing this changes X for Y URLs”. If you cannot fill both blanks, the finding goes to an appendix or out.
Group repeats. Dozens of pages with the same missing description are one finding with a long export behind it, not dozens of findings. Drop issues on URLs nobody should land on, such as parameter variants that are already canonicalized, unless the canonicalization itself is the problem. Skip vendor authority scores unless the client asked for them, and label them as vendor estimates if they did. Above all, do not paste a tool’s full issue list into an appendix titled “Findings”. It swaps your judgment for volume, and the client will read the volume as the work.
Order the report so page one decides
| Section | Contents | Length |
|---|---|---|
| Summary | The few findings that decide the next quarter, the decision you need from the client, and one line on scope | One page |
| Scope and method | The scope block from above | Half a page |
| Findings | One row per finding in the shared shape, grouped by consequence tier | As long as the evidence needs |
| Action plan | Owner, dependency and the passes-when line for each item | One table |
| Baseline | Positions, clicks and conversions as of the audit date | One table |
| Appendix | Raw exports, crawl configuration and a list of what was not checked | Links, not pages |
The baseline is easy to forget and expensive to lack. An audit with no starting measurements can say what was wrong but never what changed. Pull positions with the check settings stored, as organic rank tracking with the SERP record kept per check does, and if two sources disagree, the explanation is usually in the settings, which why two rank trackers show different positions walks through. For competitive context, name the sites that outrank the client on the baseline keywords; the competitors and share of voice view is built for that.
Put the baseline next to the audit
Branded client reports keep positions, clicks and the audit baseline in one document, so the next report starts from this one.
Local clients need one more block
For a plumber or a dental clinic, a site audit explains only part of the picture. Add a block for map visibility with a scope of its own: the keyword, the grid size and spacing, the center point and the date. Each grid point is a separate map search from exact coordinates, and the business is matched by its Google place_id rather than by name, so a similarly named competitor never gets counted as the client. How to read the resulting patterns is covered on the geo-grid rank tracking page. Keep this block apart from the site findings. One measures the website, the other measures where the business appears on the map.
Where a tool helps, and where it stops
A crawler gives you the first layer quickly and keeps the history. The Rankfinity site audit lists the affected pages for each issue, exports them as CSV and compares any two crawls, so “what changed since the last audit” becomes a lookup (site audit documentation, checked 2026-10-08). It does not know which findings matter to this client, whether a flagged page is meant to rank, or whether a developer’s fix is acceptable. That judgment is your part of the job. When the results need to sit in a branded document next to rankings, branded client reports are built for that.
Fast, with history
- Lists affected pages for each issue
- Exports them as CSV
- Compares any two crawls
Judgment
- Which findings matter to this client
- Whether a flagged page is meant to rank
- Whether a developer’s fix is acceptable
Affected pages, pass conditions, crawl history
The Rankfinity site audit keeps a record per issue, so your next report can point to a record instead of a memory.
Rebuild your last report in an hour
- Write the scope blockDates, agent, rendering, rules, parameters, robots.txt, limits, property, other sources.
- Pick the ten findings that matterThe ones that decide the next quarter.
- Add a layer and a reproduce line to eachSo a developer can see it in a minute.
- Add a passes-when lineAgree the success condition before the work starts.
- Re-crawl with the same settings after the fixThe re-crawl answers whether it is fixed.
Common questions
How long should an SEO audit report be?
Long enough to hold the evidence and short enough that page one can be read in a few minutes. Put the decisions on the first page and move exports to an appendix. If the main body runs past what the client will read, the findings are probably not grouped or ranked tightly enough.
Should an SEO audit report include a health score?
Only with its definition. If the client expects one, show it with a note on which tool and settings produced it, and lead with affected URL counts. A score that can move when a tool adds a check is weak evidence of progress.
What is the difference between an SEO audit report and an SEO report?
An audit report is a point-in-time diagnosis with a baseline and a prioritized plan. An SEO report in the monthly sense tracks outcomes against that baseline over time. Build the audit so its baseline table becomes the first column of every report that follows.
Do I need a screenshot for every finding?
No. A reproduce line and an export cover most findings. Use a screenshot where the problem is visual, such as a rendered page missing its main content, and note the date and device.
How often should I re-run the audit?
Re-run the same crawl with the same settings after each significant release and before any meeting where you will claim progress. Changing settings between runs breaks the comparison, so record them in the scope block.
Next step: take the last audit report you sent and add a scope block plus a reproduce line to its ten most important findings. Then see how Rankfinity’s site audit records affected pages, pass conditions and crawl history for each issue, so your next report can point to a record instead of a memory.