Skip to content
Tech·Beginner

How I Set Up SEO in My Next.js Portfolio from Zero

·7 min read·1,781 words
share
𝕏in

Key Takeaways

  • Next.js App Router's built-in metadata export handles all <meta> tags, OG, Twitter cards, and robots in one place
  • Google requires a 192×192px PNG favicon — a standard favicon.ico (16×32px) won't show in search results
  • Always set metadataBase first — everything else (OG images, canonical) depends on it
  • Use display: "swap" with next/font to prevent LCP penalties from font loading
  • Verify ownership in Google Search Console using the metadata.verification.google field
This post is Part 1 of the Can I Make Google Happy? series — a developer's real-world guide to making Google actually index and rank your Next.js site.

Introduction

When I built my portfolio, I didn't want to just slap a title tag on the page and call it SEO. I wanted Google to fully understand who I am, what I do, and why my site deserves to show up when someone searches "frontend developer Ahmedabad" or "hire React developer India."

This blog walks you through exactly how I set up SEO in my Next.js App Router portfolio — with real code from my actual project — and how I verified everything worked using Google Search Console.

The actual metadata config in VS Code (left) powering the live krupesh.dev portfolio (right)
The actual metadata config in VS Code (left) powering the live krupesh.dev portfolio (right)

Why `metadata` in Next.js Beats a Raw `<meta>` Tag

In traditional HTML, you'd add SEO tags like this:

html
<head>
  <title>Krupesh Vachhani | Frontend Developer</title>
  <meta name="description" content="..." />
</head>

This works — but it's manual, error-prone, and doesn't scale. Next.js App Router has a built-in metadata export in layout.tsx that handles everything automatically, including generating <title> and <meta> tags, setting canonical URLs, configuring robots behavior, and handling Open Graph and Twitter cards.

Here's the shape of what goes in src/app/layout.tsx:

ts
import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://your-domain.com"),
  title: "Your Name | Your Role",
  description: "A clear value proposition — who you are and what you build.",
  keywords: [ /* your own targeted phrases — figure these out yourself */ ],
  authors: [{ name: "Your Name", url: "https://your-domain.com" }],
  creator: "Your Name",
  alternates: {
    canonical: "https://your-domain.com",
  },
};

Let me break down each field.

Breaking Down Every Field

metadataBase

ts
metadataBase: new URL("https://your-domain.com"),

This is the base URL for your site. Next.js uses this to resolve relative paths for images in Open Graph, Twitter cards, etc. Without it, image URLs will be broken in social previews. Always set `metadataBase` first. Everything else depends on it.

title

ts
title: "Your Name | Your Role",

Format: [Your Name] | [Your Role]. Google shows ~60 characters in search results. Keep it under 60. Include your primary keyword naturally — think about what someone would actually search to find you.

description

ts
description: "Your value proposition — who you are, what you build, what makes you different.",

Google shows ~155–160 characters as the meta description snippet. Write it like a pitch — not a list of keywords. Do NOT keyword-stuff this. Google ignores it for ranking but users read it to decide whether to click.

Searched "krupesh vachani" — Google auto-corrects and krupesh.dev appears at position #2 with the exact title and description from metadata
Searched "krupesh vachani" — Google auto-corrects and krupesh.dev appears at position #2 with the exact title and description from metadata

keywords

Don't copy anyone else's keyword list here — that's how you compete with yourself. Think about what a recruiter or client in your city would actually type into Google. Your role, your stack, your location. Write those phrases yourself.

Google officially ignores the keywords meta tag for ranking, but Bing and DuckDuckGo still read it. More importantly, writing the list forces you to be intentional about what you're optimising for.

canonical

ts
alternates: {
  canonical: "https://your-domain.com",
},

A canonical URL tells Google: "This is the one true version of this page." It prevents duplicate content issues if your site is accessible at multiple URLs (e.g., www.example.com and example.com).

robots

ts
robots: {
  index: true,
  follow: true,
  googleBot: {
    index: true,
    follow: true,
    "max-image-preview": "large",
    "max-snippet": -1,
  },
},
  • index: true — Allow Google to index this page
  • follow: true — Allow Google to follow links on this page
  • max-image-preview: "large" — Google can show large preview images in search results
  • max-snippet: -1 — No limit on how much text Google can show as a snippet

How `next/font` Affects SEO

ts
import { Your_Font } from "next/font/google";

const yourFont = Your_Font({
  subsets: ["latin"],
  variable: "--font-your-font",
  display: "swap",  // ← this is the critical part
});

display: "swap" tells the browser to show the fallback font immediately, then swap to your custom font once it loads. Without swap, the browser hides text until the font loads — this causes a blank flash and hurts your LCP (Largest Contentful Paint) score, which is a Core Web Vital that directly affects your Google ranking.

next/font also self-hosts fonts — no external request to fonts.googleapis.com, which removes a DNS lookup from your critical path.

The Favicon Trap

This is the mistake I made — and almost every developer makes it. I added a favicon.ico to my Next.js project, saw it showing correctly in the browser tab, and assumed Google would pick it up. It didn't. For weeks my portfolio showed a generic grey globe icon in search results.

The reason? Google has a strict minimum size rule for favicons in search results. Google will only display your favicon if it is at least 48×48 pixels. A standard favicon.ico contains 16×16 and 32×32 sizes — both below the threshold. Google silently ignores it and falls back to the generic globe icon. This is documented in Google's favicon guidelines but almost nobody reads it until they notice the problem.

BEFORE vs AFTER — left shows krupesh.dev at #1 without a branded favicon, right shows it at #2 with the custom favicon icon after the 192×192 PNG fix
BEFORE vs AFTER — left shows krupesh.dev at #1 without a branded favicon, right shows it at #2 with the custom favicon icon after the 192×192 PNG fix

The Fix: Add a 192×192 PNG in Next.js App Router

Next.js App Router treats a file named icon.png in src/app/ as your site's primary icon and generates the correct <link rel="icon"> tag automatically.

Step 1: Get your favicon as a 192×192 PNG using Favicon.io, RealFaviconGenerator.net, or Squoosh.

Step 2: Place files in the correct locations:

code
src/app/
  favicon.ico        ← browser tab (keep this)
  icon.png           ← 192×192 PNG — Google Search reads THIS
  apple-icon.png     ← 180×180 PNG — iOS home screen

public/
  android-chrome-192x192.png   ← referenced by webmanifest
  android-chrome-512x512.png   ← referenced by webmanifest
  site.webmanifest             ← PWA manifest

Step 3: Register the icons and manifest in metadata inside layout.tsx:

ts
export const metadata: Metadata = {
  // ...your other fields
  manifest: "/site.webmanifest",
  icons: {
    icon: [
      { url: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
      { url: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png" },
    ],
    apple: [{ url: "/apple-touch-icon.png", sizes: "180x180" }],
  },
};

Step 4: Update your site.webmanifest with your real name and brand colors:

json
{
  "name": "Your Full Name",
  "short_name": "Short",
  "icons": [
    { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
  ],
  "theme_color": "#your-brand-color",
  "background_color": "#your-brand-color",
  "display": "standalone"
}

Step 5: Delete any static public/robots.txt and switch to src/app/robots.ts (covered in Blog 04).

After deploying, open Google Search Console → URL Inspection → Test Live URL on your homepage. Click "More Info" and confirm icon.png, favicon.ico, and site.webmanifest all return 200 OK. Google re-fetches favicons on its own crawl schedule — expect the correct favicon to appear within 1–2 weeks after deploying the fix.

The Quick Reference

  • favicon.ico (16×16, 32×32) — Browser tab
  • icon.png (192×192) — Google Search
  • apple-icon.png (180×180) — iOS home screen
  • android-chrome-512x512.png (512×512) — Android / PWA splash

Google Search needs the 192×192 PNG. Everything else is a bonus.

Google Search Console: Verify Your Site

Before Google indexes you, you need to prove you own the site. Go to Google Search Console → Add property → choose "URL prefix" → enter your domain → choose "HTML tag" verification method. You'll get a code like:

code
<meta name="google-site-verification" content="YOUR_TOKEN_HERE" />

In Next.js, add it to your metadata — no raw <meta> tag needed:

ts
verification: {
  google: "xxxxxxxxxxxxx",
},

Next.js automatically injects the verification meta tag in the <head>. Click "Verify" in GSC and you're done.

What to Check in GSC After Setup

Once verified, wait 24–48 hours then check:

  1. 1.Coverage report — Are your pages indexed?
  2. 2.Performance report — What queries are people using to find you?
  3. 3.URL Inspection — Paste your homepage URL, see exactly how Google sees it

The URL Inspection tool shows whether the page is indexed, the last crawl date, any indexing errors, and a live preview of how Google renders your page.

Summary Checklist

  • metadataBase set to your domain
  • title under 60 characters with primary keyword
  • description under 160 characters, written as a value proposition
  • canonical URL set
  • robots configured with max-image-preview and max-snippet
  • next/font with display: "swap" for LCP
  • src/app/icon.png at 192×192 — Google Search favicon
  • src/app/apple-icon.png at 180×180 — iOS home screen
  • site.webmanifest in public/ with correct name and brand colors
  • metadata.icons and metadata.manifest registered in layout.tsx
  • GSC URL Inspection confirms all icon files return 200 OK
  • Google verification added via metadata.verification.google
  • Site submitted and verified in Google Search Console

Resources

FAQ

Do I need to set keywords in metadata?

Google officially ignores the keywords meta tag for ranking. But Bing, DuckDuckGo, and some AI search engines still read it. More importantly, writing your target keywords explicitly forces you to think clearly about what you're optimizing for. Keep it under 10 specific phrases — keyword stuffing still looks spammy to other engines.

How long does Google take to index a new page?

For a new site with no domain authority, expect 1–4 weeks after GSC verification before pages appear in search results. You can speed this up by: (1) submitting your sitemap in GSC, (2) using the URL Inspection → Request Indexing tool, and (3) getting at least one external link pointing to your site.

What's the difference between icon.png and favicon.ico?

favicon.ico shows in browser tabs. icon.png at 192×192 is what Google shows next to your URL in search results. They serve different purposes — you need both. Google's minimum favicon display size is 48×48px, which rules out standard ICO files.