The HTML base tag: how one line can redirect every relative link
The <base href="…"> tag sets the address that every relative URL on the page is resolved against: links, images, scripts, and relative canonical and hreflang URLs. Most sites don't need one. If you have one, make it a full https:// URL on your own site, keep only one, and put it before anything else in the head that uses a URL. A wrong base can silently point every relative link at another site.
What the base tag does
Normally a relative link such as href="care-guide" is read relative to the page's own address. A base tag replaces that starting point:
Page https://example.com/coats/wool | href="care-guide" goes to |
|---|---|
| No base tag | https://example.com/coats/care-guide |
<base href="https://example.com/"> | https://example.com/care-guide: a different page |
<base href="https://staging.example.com/"> | https://staging.example.com/care-guide: a different site |
<base href="http://example.com/"> | http://example.com/care-guide: insecure, via a redirect |
Links that start with / keep the path but take the base's protocol and host, and full URLs (https://…) aren't affected at all. Only the first <base href> on the page counts; any others are ignored.
It also changes in-page links. With a base tag, href="#reviews" points to #reviews on the base URL, not on the current page, so a "jump to reviews" link can load a different page instead of scrolling.
How it goes wrong
http://, so relative links go through a redirect, and relative scripts and styles can be blocked as mixed content.javascript: or data: value, or a bare word. Browsers ignore javascript: and data: values. A URL that can't be read at all breaks every relative link in Chrome.Browsers resolve relative canonical and hreflang URLs against the base too, and search engines are likely to do the same. A wrong base could point every page's canonical somewhere else. Google already recommends full URLs in canonicals, so write them in full.
How to check by hand
1See the base and what it changes 30 seconds
Open DevTools (F12), go to the Console and paste:
// Shows the page's <base> URL(s), and which relative links point somewhere different because of it
const parse = (value, against) => { try { return new URL(value, against); } catch (e) { return null; } };
const bases = [...document.querySelectorAll('base[href]')];
const used = bases[0];
const urlEls = [...document.querySelectorAll('[href], [src]')].filter(el => el.tagName !== 'BASE');
console.table(bases.map((b, i) => {
const url = parse(b.getAttribute('href'), location.href);
const before = urlEls.filter(el => el.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING).length;
return {
base: b.getAttribute('href'),
used: i === 0 ? 'yes: the first one wins' : 'NO: ignored',
'in the head': b.closest('head') ? 'yes' : 'NO',
'URL elements before it': before,
problem: !url ? 'NOT A VALID URL'
: !/^https?:$/.test(url.protocol) ? 'NOT http(s)'
: url.host !== location.host ? 'DIFFERENT HOST: ' + url.host
: url.protocol !== location.protocol ? 'DIFFERENT PROTOCOL: ' + url.protocol
: '',
};
}));
if (!bases.length) console.log('No <base href>: relative URLs resolve against the page address');
// Only HTML <a> and <link> (not SVG links); anything without a scheme is relative, including //host/path
const relative = [...document.querySelectorAll('a[href], link[href]')]
.filter(el => el.namespaceURI === 'http://www.w3.org/1999/xhtml')
.filter(el => !/^[a-z][a-z0-9+.-]*:/i.test(el.getAttribute('href').trim()));
const changed = relative
.map(el => ({
href: el.getAttribute('href'),
'goes to': (parse(el.getAttribute('href'), document.baseURI) || { href: 'nowhere: the base is broken' }).href,
'without the base': (parse(el.getAttribute('href'), location.href) || {}).href,
note: used && (el.compareDocumentPosition(used) & Node.DOCUMENT_POSITION_FOLLOWING) ? 'before the base tag' : '',
}))
.filter(r => r['goes to'] !== r['without the base']);
if (changed.length) console.table(changed.slice(0, 20));
console.log(changed.length + ' of ' + relative.length + ' relative links point somewhere different because of the base');
It lists each base tag, which one is used, whether it's in the head, how many URL elements come before it, and whether it points at another host or protocol. Then it lists relative links that go somewhere different because of the base, with where they'd go without it.
2Check the source 30 seconds
Press Ctrl+U and search for <base. If it's there, it should be near the top of the head, before any <link>, <script> or <style>, and appear once.
3Hover over links 1 minute
Hover over a few navigation and in-page links and check the address in the bottom corner of the browser. Links that unexpectedly show another host, or an in-page link that shows a different page, point to a base tag problem.
How to fix it
- Remove it if nothing relies on it.
- If you need it, make it a full
https://URL on the live site, and put it first in the head, straight after<meta charset>. - Keep one. Find which template or plugin adds the second and remove it there.
- Write canonical and hreflang URLs in full, so they don't depend on the base at all.
How crawlers report this
If a crawl sent you here, these are the labels to look for. Our guide to Screaming Frog and Sitebulb issues covers other common ones.
| Screaming Frog | Sitebulb | What it means |
|---|---|---|
| No direct match | Base URL malformed or empty | The base tag has no usable URL, which can break every relative link on the page. |
| No direct match | Multiple base URLs | The page has more than one base tag. Only the first counts. |
Doing this on every page?
SEOSnip checks the base tag for you
Open the extension on any page and it reads every base tag. You get:
- A flag for more than one base URL, listing each with where it was found
- A flag for a base URL that isn't a valid http or https address, with the tag shown (a base on another host or on http isn't flagged; the snippet above shows those)
- Base tags in the body, through the metadata-outside-the-head check
- A ticket with that evidence, and a "Check if fixed" button that re-runs the check
Multiple base URLs are declared
| Base href | Found at |
|---|---|
https://example.com/ | head > base:nth-of-type(1) |
https://staging.example.com/ | head > base:nth-of-type(2) |