Fix: Missing title tag
Missing a title tag means Google has no name for your page — it will auto-generate one, and it will be wrong. Pages without a title tag are essentially invisible to search engines. When Googlebot parses a document and finds no <title> node within the <head>, it attempts to assemble a substitute string by scraping your <h1>, anchor text from incoming links, or random text nodes from the body. This fallback string rarely matches your intent, truncates arbitrarily, and degrades click-through rates (CTR) from the search engine results page.
Indxel catches this with the title-present rule. We assign this rule a critical severity weight of 5/100. A page failing this check lacks the absolute minimum requirement for indexing. Every route you ship requires a unique, statically analyzable title tag under 60 characters.
How to detect it
Run the Indxel CLI against your local development server or static build directory. The CLI parses the DOM of every route and validates the <head> contents against our rule engine.
npx indxel check http://localhost:3000Pages missing a title tag will fail the scan immediately and exit with a non-zero status code. The CLI outputs warnings in the same format as ESLint — one line per issue, with the file path and rule ID.
Scanning 47 routes...
app/pricing/page.tsx
✖ title-present Missing <title> tag in document <head>.
app/blog/[slug]/page.tsx
✖ title-present Dynamic route returned undefined for title property.
Found 2 critical errors across 47 pages.The title-present rule is a blocking error. In a standard CI environment, any page failing this check will fail the build. Do not disable this rule.
The fix
The fix depends on your rendering framework. You must ensure a <title> node is injected into the server-rendered HTML. Client-side injection (via useEffect) is insufficient, as crawlers often index the initial HTML payload before executing JavaScript.
Next.js App Router (Static Pages)
In the Next.js App Router, you define metadata by exporting a static metadata object from a page.tsx or layout.tsx file.
Bad: No metadata export
// app/pricing/page.tsx
// ✖ Fails title-present: No metadata export
export default function PricingPage() {
return (
<main>
<h1>Pro Plans</h1>
<PricingTable />
</main>
);
}Good: Static metadata export
// app/pricing/page.tsx
// ✔ Passes title-present
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Pricing | Acme Corp',
};
export default function PricingPage() {
return (
<main>
<h1>Pro Plans</h1>
<PricingTable />
</main>
);
}Next.js App Router (Dynamic Pages)
For dynamic routes like blog posts or product pages, you cannot use a static object. You must export the generateMetadata function. Next.js awaits this function before rendering the route.
Bad: Missing generateMetadata on a dynamic route
// app/blog/[slug]/page.tsx
// ✖ Fails title-present: Crawler sees no title for /blog/my-post
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await db.post.findUnique({ where: { slug: params.slug } });
return <article>{post.content}</article>;
}Good: Fetching data in generateMetadata
// app/blog/[slug]/page.tsx
// ✔ Passes title-present
import { Metadata } from 'next';
export async function generateMetadata({
params
}: {
params: { slug: string }
}): Promise<Metadata> {
// Next.js automatically deduplicates this fetch call
// if you call it again in the page component.
const post = await db.post.findUnique({ where: { slug: params.slug } });
if (!post) {
return { title: 'Post Not Found' };
}
return {
title: post.title,
};
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await db.post.findUnique({ where: { slug: params.slug } });
return <article>{post.content}</article>;
}Next.js Pages Router
If you maintain a legacy Next.js Pages Router application, you must use the next/head component.
// pages/about.tsx
import Head from 'next/head';
export default function About() {
return (
<>
<Head>
<title>About Us | Acme Corp</title>
</Head>
<main>
<h1>About Us</h1>
</main>
</>
);
}Plain HTML
For static sites or frameworks without abstract metadata APIs, ensure the <title> tag is the first element inside the <head> to guarantee parsers read it before encountering blocking scripts.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Enterprise Database Solutions</title>
<meta charset="utf-8">
</head>
<body>
<!-- Content -->
</body>
</html>Prevention: CI guard
Fixing the issue locally is step one. Step two is ensuring a missing title never reaches production again. Add Indxel to your continuous integration pipeline to fail the build if a developer forgets a metadata export.
Run npx indxel check --ci in your build command. This flag enforces a strict exit code 1 if any critical rules fail.
GitHub Actions
Add a dedicated job to your workflow YAML to scan your application after the build step.
# .github/workflows/seo.yml
name: SEO Check
on: [push, pull_request]
jobs:
indxel:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
# Start the local server in the background
- run: npm start &
# Run the Indxel CI guard
- name: Validate SEO
run: npx indxel check http://localhost:3000 --ci --diffUse the --diff flag in PR environments. This instructs Indxel to only scan routes that changed compared to the main branch, reducing a 2-minute scan to under 3 seconds.
Vercel Build Command
If you deploy on Vercel and want to block deployments at the infrastructure level, modify your build command in package.json to include an Indxel check on the static output.
{
"scripts": {
"dev": "next dev",
"build": "next build && npx indxel check .next/server/app --ci",
"start": "next start"
}
}Edge cases
Even with metadata exports in place, developers frequently trip over Next.js specific edge cases that trigger the title-present rule.
Client Components overriding layouts
In the App Router, you cannot export metadata from a file marked with "use client". If your entire page is a client component, you must wrap it in a server-rendered layout or a server-component page wrapper.
Fails validation:
'use client';
import { Metadata } from 'next'; // ✖ Error: Next.js ignores this
export const metadata: Metadata = {
title: 'Dashboard'
};
export default function Dashboard() {
return <div>Interactive Dashboard</div>;
}The solution: Move the client logic into a separate component, and keep the page.tsx as a Server Component.
// app/dashboard/page.tsx
import { Metadata } from 'next';
import { DashboardClient } from './dashboard-client';
export const metadata: Metadata = {
title: 'Dashboard'
};
export default function DashboardPage() {
return <DashboardClient />;
}Template inheritance and absolute titles
Next.js allows you to define a title template in your root layout (%s | Acme). If a child page exports an empty title or a null value, the template fails to compile, and the page renders without a title tag.
If a child page needs to bypass the root layout template entirely, use the absolute property.
// app/layout.tsx
export const metadata = {
title: {
template: '%s | Acme',
default: 'Acme',
},
};
// app/landing/page.tsx
export const metadata = {
title: {
// Overrides the template.
// The final title is exactly "Acme Landing Page", not "Acme Landing Page | Acme"
absolute: 'Acme Landing Page',
},
};Empty dynamic data
If your generateMetadata function relies on a database call, and that call returns null, you must provide a fallback title. Returning an empty string "" or undefined will trigger the title-present error. Always return a literal string or handle the 404 state.
Related rules
A missing title tag rarely occurs in isolation. If you triggered title-present, check your CLI output for these related rules:
title-too-long: Titles exceeding 60 characters get truncated by Google. Keep them concise.missing-meta-description: Without a description, Google pulls random body text for the SERP snippet.missing-og-title: Social platforms rely onog:title. If missing, link previews on Slack, X, and LinkedIn will render poorly.
FAQ
What happens if a page has no title tag?
Google generates a title from your page content, H1, or anchor text from other pages. The auto-generated title is often wrong, too long, or off-brand. You completely lose control over how your page appears in search results and severely damage your CTR.
Can I set a default title in my layout?
Yes. In Next.js App Router, export metadata from layout.tsx with a title.default and title.template. Pages inherit the default title automatically and can override it with their own specific string.
export const metadata = {
title: {
default: 'Acme',
template: '%s | Acme'
}
};Does Indxel check titles on every page?
Yes. The title-present rule runs on every single page Indxel scans. In CI mode (npx indxel check --ci), a missing title fails your build because it is classified as a critical rule.
How do client-side route transitions affect the title?
Next.js handles document head updates automatically during client-side navigation. When a user clicks a <Link>, Next.js fetches the RSC payload for the new route, extracts the metadata, and mutates the <title> tag in the DOM. Indxel verifies the static server response, ensuring the title is present for crawlers before JavaScript executes.
Frequently asked questions
What happens if a page has no title tag?
Google generates a title from your page content, H1, or anchor text from other pages. The auto-generated title is often wrong, too long, or off-brand. You lose control over how your page appears in search results.
Can I set a default title in my layout?
Yes. In Next.js App Router, export metadata from layout.tsx with a title.default and title.template. Pages inherit the default and override with their own title. Example: title: { default: 'Acme', template: '%s | Acme' }.
Does Indxel check titles on every page?
Yes. The title-present rule runs on every page Indxel scans. In CI mode (npx indxel check --ci), a missing title fails your build because it's a critical rule.