Skip to content

My nav icon became a Google sitelink. Here's why — and how /seo-audit caught it.

SEO Next.js 4 min read
Google search result showing 'Hamburger-menu' as a sitelink, with Iron Scrolls branding

We noticed something strange in Google Search Console.

Our site had a sitelink — that row of quick-links Google sometimes shows under a main search result. Sitelinks are great. They help users jump straight to the section they want. Ours said:

Hamburger-menu

Not “About”. Not “Book a trip”. Hamburger-menu.


Google builds sitelinks from what it can read about your pages. When there’s no real metadata to work with, it reaches for whatever text it can find — headings, link text, alt attributes.

Our mobile nav icon had this:

<Image src={menuIcon} alt="hamburger-menu" />

That was the only unique, descriptive text Google could find anywhere on that element. So it used it.

Two things made this worse:

1. No per-page <title> or <meta name="description">

Every page fell back to the root layout’s generic title, so Google couldn’t distinguish between them. With nothing to differentiate pages, it reached deeper into the markup for signals — and found the alt text.

2. No metadataBase in the root layout

Without metadataBase, relative Open Graph image URLs don’t resolve correctly. Google had nothing visual to anchor the page to, which made the text signals even more weight-bearing than they should be.


Before writing a single line of the fix, I ran /seo-audit on the project.

It found, phase by phase:

  • Phase 2 (route audit): Every page missing a title, description, and canonical. The root layout had no metadataBase.
  • Phase 3 (root layout): No default OG or Twitter Card tags. No metadataBase.
  • Phase 6 (image alt text): alt="hamburger-menu" on the mobile nav icon. Social media icons with alt="instagram-logo" and alt="social-media-icon".
  • Phase 7 (thin pages): /privacy and /terms both with no unique content — flagged as duplication candidates.
  • Phase 4 (robots/sitemap): No robots.ts. No sitemap.ts. Google crawling in the dark.

It generated two HTML reports — a developer report with file paths and line references, and a plain-English report for the client — then applied every safe fix automatically.


1. Fix the alt text

// before
<Image src={menuIcon} alt="hamburger-menu" />
// after
<Image src={menuIcon} alt="Open navigation menu" />

The rule: alt text on decorative/functional icons should describe the action, not the object. A hamburger icon opens the navigation — that’s what the alt should say.

2. Add per-page metadata to every route

app/about/page.tsx
export const metadata: Metadata = {
title: 'About — TripCooks',
description: 'We plan culinary travel experiences...',
alternates: { canonical: 'https://tripcooks.tours/about' },
openGraph: { title: 'About — TripCooks', ... },
};

For "use client" pages that can’t export metadata directly, the fix is a sibling layout.tsx server component — the correct Next.js App Router pattern. The layout is a server component and can export metadata; the page file stays a client component.

For dynamic routes using generateMetadata to fetch data by params, there is one extra step in Next.js 15: params is now a Promise and must be awaited before use.

// ✗ Next.js 14 — breaks in Next.js 15
type Props = { params: { id: string; name: string } };
export async function generateMetadata({ params }: Props) {
const trip = await fetchTrip(params.id);
}
// ✓ Next.js 15 — await params first
type Props = { params: Promise<{ id: string; name: string }> };
export async function generateMetadata({ params }: Props) {
const { id, name } = await params;
const trip = await fetchTrip(id);
}

3. Add metadataBase to the root layout

export const metadata: Metadata = {
metadataBase: new URL('https://tripcooks.tours'),
openGraph: {
siteName: 'TripCooks',
type: 'website',
locale: 'en_US',
},
twitter: { card: 'summary_large_image', site: '@tripcooks' },
};

With metadataBase set, all relative OG image paths in child pages resolve correctly. Without it, Next.js silently outputs malformed URLs and crawlers ignore the OG tags.


While it was in there, /seo-audit also:

  • Generated app/robots.ts and app/sitemap.ts (dynamic, pulling trip URLs from the CMS)
  • Added robots: { index: false } to /cart, /checkout, and /payment-success
  • Added Organization and WebSite JSON-LD structured data to the home layout
  • Added TouristTrip JSON-LD to individual trip pages

None of that was the original ask. All of it was found and fixed in the same pass.


The code is deployed. But Google’s index doesn’t update the moment you push.

Here’s what the timeline actually looks like:

Page titles and descriptions update relatively quickly — often within days of the next crawl. Google re-indexes individual pages much faster than it re-evaluates sitelinks.

Sitelinks are slow. Google decides which pages to feature as sitelinks based on its own signals, built up over time. Even after the underlying cause is removed, the old sitelink can persist in the index for weeks or months while Google re-evaluates the site structure.

The “Hamburger-menu” sitelink is still showing in the index as of this writing. The root cause — the alt attribute and missing metadata — is fixed in the deployed code. The rest is waiting for Google.

This is worth knowing before you run /seo-audit on your own site: the audit and the fixes are instant. Google’s acknowledgement of those fixes is not.


/seo-audit is one of seven commands in Iron Scrolls — a collection of Claude Code slash commands for audits, reviews, and test coverage. Clone once, available in every project.

Terminal window
git clone https://github.com/Oladiman/iron-scrolls.git
cd iron-scrolls
bash install.sh

Then in any Next.js project:

/seo-audit

Nine phases. Two HTML reports. Fixes applied.

Browse all commands →


The fix is deployed. Google is still catching up — we’ll update this post when the sitelink clears.

If these scrolls have served you well —
☕ Support on Ko-fi ♥ GitHub Sponsors