Skip to content
Issue docs

Meta refresh used as a redirect

Importantmeta_refresh_redirectIssue 122

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 a url= 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

  1. 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.

  2. Remove the tag once the redirect is in place. Leaving both means the document is still downloaded before the redirect that already happened.

  3. 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.

  4. 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.

  5. 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-page

Example 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-page

Example 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

  1. Looks in the document for a <meta> tag whose http-equiv attribute is refresh. The attribute name is matched regardless of case.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Further reading