I've seen more than once. A team ships a shiny new page template, everything looks great in the browser, and a week later someone notices that every product page in Google is titled "Home | Company." Nobody broke the layout. Nobody broke the tests. The <title> just quietly fell back to a default, on thousands of pages.
Meta tags are tiny, invisible in the UI and almost never tested, which makes them one of the easiest parts of a site to break. In this guide, I'll cover which meta tags actually matter in 2026, what Google really does with your titles and descriptions (with data, not folklore), how to implement them properly in common stacks, and how to test them so this class of bug never reaches production. A meta tag analyzer is the quickest way to see what you're actually shipping, and I'll show you how to automate the same checks in CI.
Disclosure: I work with the team behind SEO Site Checker, whose meta tag analyzer I mention below. Everything else in this guide is tool-agnostic.
The Short Version
If you only read one section, make it this one:
- Give every page a unique
<title>of roughly 50 to 60 characters that agrees with the page's H1. - Give every important page a unique meta description of roughly 150 to 160 characters.
- Get
robots,viewportandcharsetright. Breaking these hurts more than any badly worded title. -
Ignore
meta keywords. Google doesn't use it. - Render meta tags on the server and test them in CI, not just by eye.
Which Meta Tags Actually Matter in 2026
There are dozens of possible <meta> tags. Very few of them change anything. Here's the shortlist:
| Tag | What it does | Affects rankings? | Priority |
|---|---|---|---|
<title> |
Main input for the title link in search results | Yes | High |
meta name="description" |
Often used for the snippet under the title | No, but affects clicks | High |
meta name="robots" / X-Robots-Tag
|
Controls indexing and snippets | Can remove you entirely | Critical |
meta name="viewport" |
Controls mobile rendering | Indirectly | High |
meta charset |
Makes text render correctly | Indirectly | High |
link rel="canonical" |
Points to the preferred URL | Consolidates signals | High |
| Open Graph and Twitter cards | Social and chat link previews | No | Medium |
hreflang links |
Language and region versions | For multilingual sites | Situational |
meta name="keywords" |
Nothing, as far as Google is concerned | No | None |
Strictly speaking, canonical and hreflang are <link> elements, not meta tags, but every audit checks them alongside the rest, so I've included them.
Title Tags: What Google Actually Does With Them
Google builds "title links," and your title tag is only one input
The first thing to understand is that Google doesn't simply display your <title>. It generates what it calls a title link, and Google's documentation on title links lists the sources it can draw from: the <title> element, the main visible heading, og:title, other large and prominent text on the page, anchor text, and the text of links pointing to the page.
In practice, your title tag still wins most of the time. Ahrefs' study of 953,276 title tags found Google used the original title for about two-thirds of top-ranking pages and rewrote the other 33.4%. When it did rewrite, it pulled from the page's H1 about half the time. That's a strong hint: if your <title> and your H1 disagree, Google may pick the H1.
Length is about pixels, not characters
Google doesn't publish a character limit. It truncates title links to fit the device width, which works out to roughly 600 pixels on desktop. Because characters vary in width (a "W" is much wider than an "i"), there's no perfect character count, but 50 to 60 characters is a safe range for most titles.
Length also seems to affect rewrites. In the same Ahrefs study, titles wider than 600 pixels were rewritten 46.12% of the time, well above the overall rate. Long titles don't just get cut off. They get replaced.
Writing rules that survive rewrites
- Be descriptive and concise. Say what the page is, not what the site is.
- Front-load the main topic, so it survives truncation.
- Make every title distinct. Sibling pages shouldn't differ by a single word.
-
Put the brand at the end, after a delimiter like
|or-, unless it's your homepage. -
Keep the
<title>consistent with the H1. They don't need to be identical, but they should clearly describe the same thing. - Don't stuff keywords or repeat the same phrase.
Why Google rewrites your title
Google is unusually specific about this. Its documentation lists the problems that trigger a rewrite: half-empty titles, obsolete titles (an old year left in a recurring page), inaccurate titles, "micro-boilerplate" where sibling pages share near-identical titles, no clear main heading, a language mismatch between the title and the content, and a duplicated site name. Almost every one of these is a template bug, which means it's fixable in code.
A template pattern that works
Here's a small title builder with sensible fallbacks. It tries the most specific version first and drops segments until the title fits:
const BRAND = 'Acme Tools';
const MAX_LENGTH = 60;
export function buildTitle({ page, section }) {
const candidates = [
[page, section, BRAND],
[page, BRAND],
[page],
];
for (const parts of candidates) {
const title = parts.filter(Boolean).join(' | ');
if (title.length <= MAX_LENGTH) return title;
}
// Last resort: trim the page name at a word boundary
return page.slice(0, page.lastIndexOf(' ', MAX_LENGTH)).trim();
}
buildTitle({ page: 'Cordless Drill 18V', section: 'Power Tools' });
// "Cordless Drill 18V | Power Tools | Acme Tools"
The important part isn't the code. It's that the title is generated from real page data, with a deliberate fallback, instead of silently defaulting to the site name.
Meta Descriptions: Do They Still Matter?
Google rewrites most of them, so write them anyway
Here's the uncomfortable truth. Ahrefs' meta description study found Google rewrote 62.78% of meta descriptions, because it tailors snippets to each search query. A quarter of top-ranking pages didn't even have one.
So why bother? Because when your description is shown, it's your pitch in the search results, and a good one earns the click. It's also what most social platforms and chat apps fall back on for link previews. Google's guidance on meta descriptions says it uses them when they describe the page more accurately than the on-page text, which is a good reason to write them well.
Length
Again, there's no official limit. Snippets are truncated to fit the screen, commonly measured at around 920 pixels on desktop. Aim for roughly 150 to 160 characters, and put the key message in the first 120 or so, since mobile results often show less.
Writing rules
- Make each one unique. Duplicate descriptions across a site are a missed opportunity.
- Summarize the whole page, not just the first paragraph.
- Include useful specifics, like price, date, author, availability or location.
- Skip keyword lists. Google explicitly advises against them.
- Match the searcher's intent. Tell them what they'll get by clicking.
Generating them programmatically
For large, database-driven sites, writing every description by hand isn't realistic, and Google says generated descriptions are fine as long as they're readable and varied. Build them from real fields, with a clean fallback:
const stripHtml = (s) => s.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
function truncate(text, max) {
if (text.length <= max) return text;
return text.slice(0, text.lastIndexOf(' ', max - 1)) + '…';
}
export function productDescription(p) {
const parts = [
`${p.name} by ${p.brand}`,
p.price && `from $${p.price}`,
p.keyBenefit,
].filter(Boolean);
let text = parts.join(', ') + '.';
// Too thin? Fall back to a cleaned excerpt of the product copy
if (text.length < 70 && p.body) text = stripHtml(p.body);
return truncate(text, 160);
}
Implementing Meta Tags in Your Stack
Plain HTML
For reference, here's a complete, correct <head> for a typical page. Note that charset comes first, within the first 1024 bytes of the document:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cordless Drill 18V | Power Tools | Acme Tools</title>
<meta name="description" content="Lightweight 18V cordless drill with two batteries and a 3-year warranty. Free shipping on orders over $50.">
<link rel="canonical" href="https://example.com/products/cordless-drill-18v">
<meta property="og:title" content="Cordless Drill 18V | Acme Tools">
<meta property="og:description" content="Lightweight 18V cordless drill with two batteries and a 3-year warranty.">
<meta property="og:image" content="https://example.com/images/drill-og.jpg">
<meta name="twitter:card" content="summary_large_image">
</head>
Next.js App Router
Next.js has a built-in Metadata API, and it handles most of this for you. Set a title template and a base URL once in the root layout:
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: { default: 'Acme Tools', template: '%s | Acme Tools' },
description: 'Quality power tools with free shipping on orders over $50.',
};
Then generate page-specific metadata for dynamic routes:
// app/products/[slug]/page.tsx
import type { Metadata } from 'next';
type Props = { params: Promise<{ slug: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const product = await getProduct(slug);
return {
title: product.name, // becomes "Cordless Drill 18V | Acme Tools"
description: product.summary,
alternates: { canonical: `/products/${slug}` },
openGraph: { images: [product.image] },
};
}
Because this runs on the server, the tags are in the initial HTML response, which is exactly where you want them.
Astro and other static site generators
With Astro, a shared SEO component keeps every page consistent. Each page passes props from its front matter:
---
// src/components/SEO.astro
interface Props { title: string; description: string; image?: string }
const { title, description, image = '/og-default.jpg' } = Astro.props;
const fullTitle = `${title} | Acme Tools`;
const canonical = new URL(Astro.url.pathname, Astro.site);
---
<title>{fullTitle}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<meta property="og:title" content={fullTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.site)} />
The same pattern works in Hugo, Eleventy or any template engine: one component, required props and no silent defaults.
Single-page apps and client-side rendering
This is where most meta tag bugs live. If your tags are injected by JavaScript after the page loads, for example with React Helmet in a client-only app, the initial HTML contains whatever default was in index.html. Google does render JavaScript, but rendering can happen later than crawling. Most social media crawlers and chat apps don't run JavaScript at all, so your link previews will show the default title and image forever.
The fix is to render meta tags on the server, through SSR, static generation or prerendering. To see what non-JavaScript crawlers see, just fetch the page with curl. If the right title isn't in that response, it isn't really there.
CMS templates and fallbacks
If editors manage content in a CMS, make the SEO title and description fields required, or at least generate a sensible fallback from the page title and excerpt. Never ship an empty <title>, and never fall back to one site-wide description on every page. Both turn into duplicate-content warnings at scale.
The Other Meta Tags Developers Break
A few more tags cause outsized damage when they go wrong:
-
noindexleft on from staging. Check both the<meta name="robots">tag and theX-Robots-TagHTTP header. The header version is often set at the server or CDN level and won't show in your page source. -
A missing or wrong
viewport. Without it, mobile browsers render a zoomed-out desktop layout. -
Charset declared too late. If
<meta charset>isn't near the top of the<head>, browsers may guess the encoding and mangle special characters. - Broken Open Graph images. Use absolute URLs, a large enough image (1200 × 630 pixels is a common target), and make sure the image isn't blocked by robots.txt.
- Canonicals pointing to the wrong place. A canonical that points to a redirect, a 404 or the homepage tells Google to ignore the page you actually want indexed.
How to Test Your Meta Tags
Use a meta tag analyzer for a quick check
For a fast, visual check of any public URL, a free meta tag analyzer shows the live title and description with their lengths, the robots and viewport values, whether Open Graph tags are present, and a preview of how the result looks in Google. It's the quickest way to catch truncation and missing tags before a release, and it works on competitors' pages too, which is handy when you're benchmarking.
Check the raw response with curl and DevTools
For the ground truth, look at exactly what the server sends:
# Title, description, robots and Open Graph tags in the raw HTML
curl -s https://example.com/products/cordless-drill-18v \
| grep -iE '<title>|name="description"|name="robots"|property="og:'
# The X-Robots-Tag header, which won't show in the page source
curl -sI https://example.com/products/cordless-drill-18v | grep -i x-robots-tag
In DevTools, compare View Source (the raw response) with the Elements panel (the rendered DOM). If they disagree on the title, you're relying on client-side rendering.
Automate it in CI
Manual checks don't scale, and they don't catch regressions. Here's a small Node script (Node 18 or newer, which has fetch built in) that fetches one URL per template and fails the build on the most common problems:
// scripts/check-meta.mjs
// Usage: node scripts/check-meta.mjs https://staging.example.com/ https://staging.example.com/products/sample
const urls = process.argv.slice(2);
const seenTitles = new Map();
let failed = false;
const fail = (url, msg) => { console.error(`FAIL ${url}: ${msg}`); failed = true; };
const getMeta = (html, name) =>
html.match(new RegExp(`<meta\\s+name=["']${name}["']\\s+content=["']([^"']*)["']`, 'i'))?.[1];
for (const url of urls) {
const res = await fetch(url);
const html = await res.text();
const title = html.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.trim();
const description = getMeta(html, 'description');
const robots = `${getMeta(html, 'robots') ?? ''} ${res.headers.get('x-robots-tag') ?? ''}`;
if (!title) fail(url, 'missing <title>');
else {
if (title.length > 60) fail(url, `title is ${title.length} characters`);
if (seenTitles.has(title)) fail(url, `duplicate title, also on ${seenTitles.get(title)}`);
seenTitles.set(title, url);
}
if (!description) fail(url, 'missing meta description');
if (/noindex/i.test(robots)) fail(url, 'noindex found');
}
process.exit(failed ? 1 : 0);
The regexes assume your own templates write attributes in a consistent order, which is fine for checking your own output. If you need to handle arbitrary HTML, swap them for a proper parser like cheerio. Run it against staging after each deploy, and point it at production URLs too, to catch a stray noindex before Google does.
Common Mistakes Checklist
Paste this into a PR description or a ticket and tick it off for every new template:
- [ ] Every template outputs a unique <title>, around 50 to 60 characters
- [ ] <title> and H1 describe the same thing
- [ ] Brand name at the end of titles, not the start (except the homepage)
- [ ] No duplicate titles on paginated or filtered pages
- [ ] Unique meta description on every important page
- [ ] Meta tags present in the server HTML, not injected client-side
- [ ] No noindex in meta tags or X-Robots-Tag headers on production
- [ ] viewport and charset set correctly, with charset near the top of <head>
- [ ] Canonical points to the correct, indexable URL
- [ ] og:title, og:description and an absolute og:image URL present
If you want the wider list beyond meta tags, this full reference of SEO checks explains each one and how to fix a failing result.
FAQs
What is a meta tag analyzer?
A meta tag analyzer is a tool that fetches a web page and reports its meta tags, such as the title, meta description, robots, viewport and Open Graph tags. Most also show character lengths, flag missing or duplicate tags, and preview how the page will look in Google's search results.
How long should a title tag be?
Google has no official limit, but it truncates title links at roughly 600 pixels on desktop, which is about 50 to 60 characters for most titles. Longer titles are also more likely to be rewritten. Put the most important words first so they survive truncation.
How long should a meta description be?
There's no official limit, but snippets are usually truncated at around 920 pixels on desktop, so roughly 150 to 160 characters is a sensible target. Put the key message in the first 120 characters, because mobile results often show less.
Does Google still use meta descriptions?
Yes, but not always. Google often generates its own snippet from page content to match a specific search, and one large study found it rewrote about 63% of meta descriptions. A well-written description is still worth having, because it's often shown and can improve your click-through rate.
Do meta keywords still matter for SEO?
No. Google has ignored the meta keywords tag for ranking for many years. It does no harm, but it does no good either. Spend the time on unique titles and descriptions instead, which actually affect how your pages appear in search.
Why is Google showing a different title than my title tag?
Google generates title links from several sources and may rewrite your title if it's too long, vague, duplicated across similar pages, outdated, or doesn't match the page's main heading. Keeping your title concise, unique and consistent with your H1 makes rewrites much less likely.
Wrapping Up
Treat meta tags like any other output of your code. Generate them from real data, render them on the server, and test them automatically. Ten minutes setting up a CI check is a lot cheaper than discovering, three months later, that every product page has been showing up in Google as "Home | Company."
Top comments (0)