back to blog
Tech··~7 min·Beginner

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

#nextjs#seo#metadata#google-search-console#web-vitals
Written with:Next.jsGoogle Search Consolefavicon.io

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.

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 exactly how I set it up in my portfolio's src/app/layout.tsx:

ts
import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://krupesh.dev"),
  title: "Krupesh Vachhani | Frontend Developer",
  description:
    "Portfolio of Krupesh Vachhani – Frontend Developer with 1+ years of experience building scalable React.js and Next.js applications.",
  keywords: [
    "Krupesh Vachhani",
    "Frontend Developer",
    "React developer",
    "Next.js developer",
    "hire frontend developer India",
    "frontend developer Ahmedabad",
  ],
  authors: [{ name: "Krupesh Vachhani", url: "https://krupesh.dev" }],
  creator: "Krupesh Vachhani",
  alternates: {
    canonical: "https://krupesh.dev",
  },
};

Let me break down each field.

Breaking Down Every Field

metadataBase

ts
metadataBase: new URL("https://krupesh.dev"),

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: "Krupesh Vachhani | Frontend Developer",

Format: [Your Name] | [Your Role]. Google shows ~60 characters in search results. Keep it under 60. Include your primary keyword (Frontend Developer) naturally.

description

ts
description: "Portfolio of Krupesh Vachhani – Frontend Developer with 1+ years of experience...",

Google shows ~155–160 characters as the meta description snippet. Write it like a value proposition — who you are, what you build, what makes you different. Do NOT keyword-stuff this. Google ignores it for ranking but users read it to decide whether to click.

keywords

ts
keywords: ["Frontend Developer", "React developer", "hire frontend developer India"],

Google officially says it ignores the keywords meta tag — but other search engines (Bing, DuckDuckGo) may use it. More importantly, listing them here forces you to think clearly about what terms you want to rank for.

canonical

ts
alternates: {
  canonical: "https://krupesh.dev",
},

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.krupesh.dev and krupesh.dev).

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 { Fira_Code } from "next/font/google";

const firaCode = Fira_Code({
  subsets: ["latin"],
  weight: ["300", "400", "500", "600", "700"],
  variable: "--font-fira-code",
  display: "swap",
});

display: "swap" tells the browser to show the fallback font immediately, then swap to Fira Code 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.

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 = {
  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": "Krupesh Vachhani",
  "short_name": "Krupesh",
  "icons": [
    { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
  ],
  "theme_color": "#0a0a0a",
  "background_color": "#0a0a0a",
  "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 https://krupesh.dev → choose "HTML tag" verification method. You'll get a code like:

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

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