My nav icon became a Google sitelink. Here's why — and how /seo-audit caught it.
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.
What happened
Section titled “What happened”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.
What /seo-audit found
Section titled “What /seo-audit found”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, andcanonical. The root layout had nometadataBase. - 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 withalt="instagram-logo"andalt="social-media-icon". - Phase 7 (thin pages):
/privacyand/termsboth with no unique content — flagged as duplication candidates. - Phase 4 (robots/sitemap): No
robots.ts. Nositemap.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.
The three root fixes
Section titled “The three root fixes”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
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 15type Props = { params: { id: string; name: string } };
export async function generateMetadata({ params }: Props) { const trip = await fetchTrip(params.id);}
// ✓ Next.js 15 — await params firsttype 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.
The bonus fixes
Section titled “The bonus fixes”While it was in there, /seo-audit also:
- Generated
app/robots.tsandapp/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.
What happens after the fix
Section titled “What happens after the fix”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.
The command
Section titled “The command”/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.
git clone https://github.com/Oladiman/iron-scrolls.gitcd iron-scrollsbash install.shThen in any Next.js project:
/seo-auditNine phases. Two HTML reports. Fixes applied.
The fix is deployed. Google is still catching up — we’ll update this post when the sitelink clears.