Meta refresh used as a redirect
What is this issue?
This check reports a page that redirects using
<meta http-equiv="refresh" content="0;url=..."> instead of an HTTP status
code.
A meta refresh is a redirect written into the HTML. The server returns 200 and a complete document, and the browser then navigates somewhere else after the delay the tag asks for.
A refresh that lands on the same page is a reload, not a redirect, and is not
reported. Both spellings are excluded: content="30" with no target at all, and
content="30; url=/dashboard" written on /dashboard, which is what a status
board or a kiosk page normally writes. Only the fragment is ignored in that
comparison, so url=#top is a reload too.
For a page to pass this check:
- It does not carry a
<meta http-equiv="refresh">tag with aurl=target. Redirects are performed with a 301 or 302 from the server.
Why it matters
Google does follow meta refreshes, and says so. It also says server-side redirects are preferred, and the reasons are practical:
- The weakest redirect signal. A 301 is an unambiguous statement that the content has moved permanently. A meta refresh has to be interpreted, is treated as a hint, and consolidates ranking signals more slowly and less completely.
- Two requests instead of one. The original page is downloaded in full before the browser moves on, so every visitor pays for a document nobody reads.
- Accessibility. A delayed refresh moves the page under a reader who has started reading it, which fails WCAG 2.2.1 unless the delay can be turned off.
- The back button breaks. Depending on the delay, going back re-triggers the refresh and the reader is trapped.
- It is often a leftover. Meta refreshes tend to survive from an older version of a site where nobody had server access.
Graded important: the page is still reachable and still indexable, so this is not critical, but the ranking cost is real and measurable.
How to fix it
Replace it with a server redirect. A permanent move is a 301; a temporary one is a 302. Configure it in the web server, the CDN, or the application's routing.
Remove the tag once the redirect is in place. Leaving both means the document is still downloaded before the redirect that already happened.
If you cannot reach the server configuration, a redirect at the CDN or a platform-level redirect rule is the next best thing; a meta refresh is the last resort, and then only with
content="0;url=..."so no reader ever sees the page.Never use a delayed refresh for a redirect. If the page is genuinely a "you are being redirected" interstitial, it needs a visible link and a way to stop the timer.
Update the links. If internal links still point at the redirecting page, point them at the destination instead so the hop is not paid at all.
Examples
Example 1: A server-side redirect
Scenario: A renamed page.
Passes because: the move is expressed in the status line, and no document is downloaded on the way.
GET /old-page -> 301 Moved Permanently
Location: /new-pageExample 2: An instant meta refresh
Scenario: A redirect added by someone without access to the server configuration.
Fails because: the redirect is in the HTML, so the whole page is fetched before the browser moves on.
<head>
<meta http-equiv="refresh" content="0;url=https://example.com/new-page">
</head>Corrected version: move it to the server and delete the tag.
GET /old-page -> 301 Moved Permanently
Location: https://example.com/new-pageExample 3: A refresh that is not a redirect
Scenario: A status dashboard that reloads itself every thirty seconds.
Passes because: there is no url= target, so this is a reload of the same
page rather than a redirect somewhere else.
<meta http-equiv="refresh" content="30">How PixyScan detects this
Looks in the document for a
<meta>tag whosehttp-equivattribute isrefresh. The attribute name is matched regardless of case.Reads the first such tag only. A browser acts on the first refresh directive it meets and ignores the rest, so a page with several of them performs one redirect and is one finding.
Parses the content attribute. The value is a delay followed by a
url=target. Whitespace around the parts is tolerated,URL=in any case is recognised, and a target wrapped in quotes is unwrapped.Requires a target somewhere else. A refresh with only a delay --
content="30"-- is a reload. So is one whose target resolves to this same page, which is how most self-refreshing pages are actually written. Neither is reported. The comparison ignores the fragment.Reports the tag verbatim, along with the delay and the destination, so what has to be replaced is on the row.
What we store
Storage Level
Page Level
Database Table / Prisma Model
audit_issues.details
Stored Fields
| Field | Type | Description |
|---|---|---|
| metaRefreshContent | String | The content attribute exactly as written |
| delaySeconds | Float | The delay the tag asks for, or null if none was given |
| targetUrl | String | The address the tag redirects to, as written |
Detection Dependencies
- HTML Document
Note
Read from the document rather than from the response, so it is stored on the finding. Only the FIRST refresh tag is recorded: a browser honours the first and ignores the rest, so a page with two of them performs one redirect.