All fixes
warning
Rule: h1-presentWeight: 8/100

Fix: Multiple H1 headings

Having more than one H1 on a page dilutes the main topic signal for search engines. It forces Google's crawler to guess which string of text accurately describes the document. In component-driven frameworks like Next.js or React, this collision happens frequently when layout logic is isolated from page content. A <Header> component renders an H1 for the site logo, while the <Page> component renders an H1 for the article title. The resulting DOM contains competing root nodes.

Indxel's h1-present rule flags multiple H1s as a warning. We assign this an impact weight of 8/100. While Google's indexer will not drop your page from the index for this infraction, it degrades the precision of your keyword targeting. A single, deterministic H1 tag provides the strongest possible signal for what the page is about.

How do you detect multiple H1 headings?

Run the Indxel CLI in crawl mode to scan your local development server or staging environment. The CLI parses the fully rendered DOM, executing JavaScript to catch H1s injected dynamically by client-side components.

npx indxel check --crawl http://localhost:3000
 
# Output
[WARNING] h1-present: Multiple H1 tags found (Count: 2)
File: app/blog/[slug]/page.tsx
URL: /blog/migrating-to-app-router
Rule ID: h1-present | Severity: Warning | Weight: 8/100
 
[WARNING] h1-present: Multiple H1 tags found (Count: 2)
File: app/docs/layout.tsx
URL: /docs/installation
Rule ID: h1-present | Severity: Warning | Weight: 8/100
 
Score: 92/100
Found 0 critical errors, 2 warnings.

The h1-present rule triggers a warning, not a failure, because multiple H1s are technically valid in the HTML5 specification. However, SEO parsers still penalize the pattern. Indxel defaults to failing builds only on critical errors (like missing title tags), allowing this warning to pass CI unless strictly configured.

How to fix multiple H1 headings in Next.js?

The fix requires auditing your layout tree. You must decouple your visual design from your document outline. The heading level (<h1>, <h2>) communicates structure to the crawler. Your CSS classes (text-4xl, font-bold) communicate visual hierarchy to the user.

1. Fix the Layout Component collision (App Router)

The most common source of multiple H1s is the global navigation. Developers wrap the site logo in an H1 inside app/layout.tsx or components/Header.tsx. When a user navigates to a blog post, the page renders a second H1 for the article title.

Bad: Two H1s in the DOM.

// components/Header.tsx
export function Header() {
  return (
    <header className="flex w-full items-center">
      {/* ❌ First H1 injected on every page */}
      <h1 className="text-xl font-bold tracking-tight">
        <Link href="/">Indxel</Link>
      </h1>
      <nav>...</nav>
    </header>
  );
}
 
// app/blog/[slug]/page.tsx
export default function BlogPost({ title }) {
  return (
    <article>
      {/* ❌ Second H1 injected by the page */}
      <h1 className="text-4xl font-extrabold">{title}</h1>
      <PostBody />
    </article>
  );
}

Good: The layout uses a <span> or <div> for the logo. Only the specific page component renders the H1.

// components/Header.tsx
export function Header() {
  return (
    <header className="flex w-full items-center">
      {/* ✅ Downgraded to a Link with visual styling */}
      <Link href="/" className="text-xl font-bold tracking-tight">
        Indxel
      </Link>
      <nav>...</nav>
    </header>
  );
}

2. Downgrade extra semantic headings to H2

If a page features a massive hero section followed by a prominent secondary section, developers often use two H1 tags to enforce the font size. Map the secondary heading to an <h2> and maintain the visual weight using Tailwind or CSS modules.

Bad: Tying CSS size to HTML semantics.

<main>
  <h1 className="text-6xl">Developer-first SEO</h1>
  <section>
    {/* ❌ Second H1 used just for the 6xl font size */}
    <h1 className="text-6xl mt-24">Ship faster.</h1>
  </section>
</main>

Good: Semantic HTML, identical visual output.

<main>
  <h1 className="text-6xl">Developer-first SEO</h1>
  <section>
    {/* ✅ Semantic H2, visual 6xl */}
    <h2 className="text-6xl mt-24">Ship faster.</h2>
  </section>
</main>

3. Override MDX Component Mappings

If your application renders user-generated markdown or MDX files, users will inevitably write # Title inside the content body. If your page template already provides an H1, the MDX parser will inject a second one.

Intercept the MDX rendering pipeline and downgrade root markdown headings (#) to <h2> tags.

// components/MDXComponents.tsx
import { MDXRemote } from 'next-mdx-remote/rsc';
 
const components = {
  // ✅ Map single hash (#) to an H2 to prevent H1 collisions with the page layout
  h1: (props: any) => (
    <h2 className="text-3xl font-bold mt-8 mb-4" {...props} />
  ),
  h2: (props: any) => (
    <h3 className="text-2xl font-semibold mt-6 mb-3" {...props} />
  ),
};
 
export function PostBody({ content }: { content: string }) {
  return <MDXRemote source={content} components={components} />;
}

How to guard against H1 regressions in CI?

Add a validation step to your CI pipeline that fails the build if a pull request introduces a second H1 to an existing template. Use the --diff flag to only check routes modified by the current branch.

npx indxel check --ci --diff --strict

Integrate this into GitHub Actions. By passing --strict, Indxel elevates the h1-present rule from a warning to a critical error, returning a non-zero exit code (exit 1) and blocking the merge.

# .github/workflows/seo-guard.yml
name: Indxel SEO Guard
on: [pull_request]
 
jobs:
  validate-dom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Install dependencies
        run: npm ci
 
      - name: Build Next.js app
        run: npm run build
 
      - name: Start server in background
        run: npm run start &
        
      - name: Wait for server
        run: npx wait-on http://localhost:3000
        
      - name: Check SEO regressions
        run: npx indxel check http://localhost:3000 --ci --diff --strict

Running Indxel against a built production server (npm run start) is infinitely more accurate than running it against the dev server (npm run dev). Development servers inject hot-reloading scripts and error overlays that can mutate the DOM structure and trigger false positives.

What are the edge cases for multiple H1s?

CSS hidden elements (display: none)

Responsive design often leads to duplicated DOM nodes. Developers render one layout for mobile and one for desktop, hiding the inactive layout via CSS classes like Tailwind's hidden or md:block.

// ❌ Googlebot sees two H1s in the DOM
<div className="md:hidden">
  <h1>Mobile Title</h1>
</div>
<div className="hidden md:block">
  <h1>Desktop Title</h1>
</div>

Googlebot parses the DOM, not just the pixels on the screen. It executes JavaScript, reads both H1 tags, and flags the duplicate. Furthermore, hiding an H1 with CSS triggers spam filters associated with keyword stuffing.

The fix: Render a single H1 and use CSS to adjust its styling across breakpoints, or conditionally render the DOM nodes using React state (though hydration mismatch rules apply).

// ✅ One DOM node, handled via CSS media queries
<h1 className="text-xl md:text-4xl text-center md:text-left">
  Responsive Title
</h1>

Hydration mismatches injecting duplicates

If your React component renders an H1 only on the client (useEffect), the initial HTML sent from the server lacks the H1. If a layout component provides a fallback H1 during SSR, the client-side hydration might inject the second H1 without removing the first, resulting in two H1s. Indxel's crawler waits for network idle and hydration to complete before evaluating the h1-present rule to catch this specific race condition.

Related rules

  • missing-h1: Triggered when you remove the duplicate H1s but forget to leave exactly one on the page. Carries a higher severity (Critical, weight 25/100).
  • thin-content: Often co-occurs with multiple H1s on scaffolding pages or auto-generated taxonomy routes that lack actual body content.

Frequently Asked Questions

Is having two H1 tags a big SEO problem?

It is not catastrophic, but it is sub-optimal. Google can handle multiple H1s, but a single clear H1 gives the strongest topic signal. Indxel flags it as a warning, not a critical error.

Did HTML5 make multiple H1 tags valid?

Yes, the HTML5 specification introduced the document outline algorithm, allowing an <h1> inside every <section> or <article>. However, browser vendors never fully implemented the algorithm, and Google strongly prefers a single H1 per document to determine the primary entity of the page.

Can I hide the second H1 with CSS?

No. Googlebot executes CSS and JavaScript. Hiding an H1 via display: none or visibility: hidden signals deceptive practices (keyword stuffing) and carries heavier penalties than simply having two visible H1 tags. Always remove the node from the DOM.

Should the logo be an H1 on the homepage?

Only if the homepage has no other editorial title. If your homepage features a hero section with a specific value proposition ("The developer-first SEO platform"), that text should be the H1. The logo should be an embedded SVG inside a standard anchor tag.

Frequently asked questions

Is having two H1 tags a big SEO problem?

It's not catastrophic, but it's sub-optimal. Google can handle multiple H1s, but a single clear H1 gives the strongest topic signal. Indxel flags it as a warning, not a critical error.

Catch this before it ships

$npx indxel check --ci
Get startedBrowse all fixes
Indxel

SEO validation that runs in your terminal and blocks bad deploys.

GitHubnpm

Product

  • Documentation
  • Pricing
  • Plus Plan
  • CI/CD Guard
  • Indexation
  • Free Tools
  • Blog

Comparisons

  • vs Semrush
  • vs Ahrefs
  • vs Moz
  • vs Screaming Frog
  • All comparisons

Integrations

  • Vercel
  • GitHub Actions
  • Netlify
  • Docker
  • All integrations

Resources

  • Frameworks & use cases
  • Next.js
  • For freelancers
  • For agencies
  • SEO Glossary

Built with care. MIT Licensed.

PrivacyTermsLegalContact