惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
N
News and Events Feed by Topic
S
Schneier on Security
G
GRAHAM CLULEY
Scott Helme
Scott Helme
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
CERT Recently Published Vulnerability Notes
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
Cisco Talos Blog
Cisco Talos Blog
P
Privacy & Cybersecurity Law Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
Blog — PlanetScale
Blog — PlanetScale
Project Zero
Project Zero
MyScale Blog
MyScale Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
The Last Watchdog
The Last Watchdog
Vercel News
Vercel News
The Cloudflare Blog
C
Check Point Blog
Help Net Security
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
NISL@THU
NISL@THU
S
Security @ Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
SegmentFault 最新的问题
MongoDB | Blog
MongoDB | Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
AWS News Blog
AWS News Blog
Cloudbric
Cloudbric
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Vulnerabilities – Threatpost
S
Secure Thoughts

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
How I Optimized My Next.js 15 Portfolio for Google Search (and won)
Abosi Godwin · 2026-05-11 · via DEV Community

I'm not a marketing person. I'm a developer. So when I decided to take SEO seriously on my portfolio, I approached it the same way I approach everything else — read, implement, measure, iterate.

This is the exact process I followed on my Next.js 15 portfolio to get it ranking on Google. Not theory. The actual implementation, the mistakes I made, and what finally worked.


Why most developer portfolios rank for nothing

Before the technical stuff, let me say the quiet part out loud.

Most portfolio sites are invisible to Google — not because they're badly built, but because they're built only for humans who already have the URL. No metadata. No structured data. Generic <title> tags that say "Portfolio" or the developer's name and nothing else.

Google doesn't know what you do, who you do it for, or where you are. So when a founder searches "Next.js developer Nigeria" or "hire React developer freelance", your portfolio doesn't show up — even if you're exactly the right person for the job.

Here's how I fixed that.


The Stack

  • Framework: Next.js 15 (App Router)
  • Styling: Tailwind CSS v4
  • Animation: Motion v12
  • Deployment: Vercel

Step 1: Treat Every Page as a Unique Document

The most common SEO mistake in Next.js App Router projects is reusing the same metadata object across pages — or worse, only defining it in the root layout.tsx and leaving all other pages to inherit it.

Every page on your site is a separate document as far as Google is concerned. Each one needs its own title and description.

In Next.js 15, this is clean to implement:

// app/page.tsx (homepage)
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Abosi Godwin — Next.js & React Developer in Nigeria",
  description:
    "Full-stack web developer specialising in Next.js, React, Supabase and Tailwind CSS. Based in Asaba, Nigeria. Available for freelance projects.",
};

Enter fullscreen mode Exit fullscreen mode

// app/projects/page.tsx
export const metadata: Metadata = {
  title: "Projects — Abosi Godwin",
  description:
    "A selection of production web apps built with Next.js, Supabase, Firebase, and Tailwind CSS.",
};

Enter fullscreen mode Exit fullscreen mode

Notice two things:

  1. The homepage title includes what I do and where I am. This is keyword-loaded on purpose. Founders searching for a developer in Nigeria will match that.
  2. The sub-page titles follow a consistent Page Name — Your Name format. This is good for brand recognition in search results.

Step 2: Open Graph Metadata (the one most developers skip)

Open Graph tags control how your site looks when shared on Twitter/X, LinkedIn, WhatsApp, and in iMessage previews. They also give Google additional context about your content.

Without OG tags, link previews are ugly and generic. With them, your portfolio looks professional every time someone shares it.

export const metadata: Metadata = {
  title: "Abosi Godwin — Next.js & React Developer in Nigeria",
  description: "Full-stack web developer...",
  openGraph: {
    title: "Abosi Godwin — Next.js & React Developer",
    description: "Building fast, production-ready web apps with Next.js and Supabase.",
    url: "https://abosi.vercel.app",
    siteName: "Abosi Godwin",
    images: [
      {
        url: "https://abosi.vercel.app/og-image.png",
        width: 1200,
        height: 630,
        alt: "Abosi Godwin — Next.js Developer Portfolio",
      },
    ],
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "Abosi Godwin — Next.js & React Developer",
    description: "Building fast, production-ready web apps with Next.js and Supabase.",
    images: ["https://abosi.vercel.app/og-image.png"],
  },
};

Enter fullscreen mode Exit fullscreen mode

The OG image matters. A 1200×630px image with your name, title, and a clean design is worth the 30 minutes it takes to make in Figma. I used a dark background with my name, role, and a subtle grid — nothing fancy, but it looks deliberate.

⚠️ Common mistake: Using a relative path for the OG image URL. It must be an absolute URL (starting with https://). I got burned by this — the preview was broken everywhere until I caught it.


Step 3: JSON-LD Structured Data

This is the one that most developers — even experienced ones — skip entirely. Structured data tells Google exactly what kind of entity your page represents. For a portfolio, you want two schemas: Person and WebSite.

Create a reusable component:

// components/JsonLd.tsx
export function PersonJsonLd() {
  const schema = {
    "@context": "https://schema.org",
    "@type": "Person",
    name: "Abosi Godwin",
    url: "https://abosi.vercel.app",
    jobTitle: "Full-Stack Web Developer",
    worksFor: {
      "@type": "Organization",
      name: "Freelance",
    },
    address: {
      "@type": "PostalAddress",
      addressLocality: "Asaba",
      addressRegion: "Delta State",
      addressCountry: "NG",
    },
    sameAs: [
      "https://github.com/Abosi-Godwin",
      "https://linkedin.com/in/abosigodwin",
    ],
    knowsAbout: [
      "Next.js",
      "React",
      "TypeScript",
      "Supabase",
      "Firebase",
      "Tailwind CSS",
      "Full-Stack Development",
    ],
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  );
}

Enter fullscreen mode Exit fullscreen mode

Then drop it in your root layout.tsx:

// app/layout.tsx
import { PersonJsonLd } from "@/components/JsonLd";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <PersonJsonLd />
        {children}
      </body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

Google's Rich Results Test will confirm it's being read correctly. Once indexed, your name, location, and skills can appear directly in Knowledge Panel results.


Step 4: The Server/Client Component Split for Metadata

This one trips up a lot of Next.js developers. The metadata export only works in Server Components. The moment you add "use client" to a page file, you lose the ability to export metadata from it.

The fix is simple but important: separate your page shell (server) from your interactive UI (client).

// app/projects/page.tsx — SERVER COMPONENT
import type { Metadata } from "next";
import { ProjectsClient } from "./ProjectsClient"; // client component

export const metadata: Metadata = {
  title: "Projects — Abosi Godwin",
  description: "Production web apps built with Next.js, Supabase, and Firebase.",
};

export default function ProjectsPage() {
  return <ProjectsClient />;
}

Enter fullscreen mode Exit fullscreen mode

// app/projects/ProjectsClient.tsx — CLIENT COMPONENT
"use client";

import { motion } from "motion/react";
// all your interactive/animated UI here

Enter fullscreen mode Exit fullscreen mode

This pattern lets you have both: full metadata control on every page and animations, state, and client-side hooks wherever you need them.


Step 5: sitemap.ts and robots.ts

Next.js 15 has built-in support for generating your sitemap and robots file dynamically. This is two files and 30 minutes of work that directly improves how Google crawls your site.

// app/sitemap.ts
import type { MetadataRoute } from "next";

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: "https://abosi.vercel.app",
      lastModified: new Date(),
      changeFrequency: "monthly",
      priority: 1,
    },
    {
      url: "https://abosi.vercel.app/projects",
      lastModified: new Date(),
      changeFrequency: "weekly",
      priority: 0.8,
    },
  ];
}

Enter fullscreen mode Exit fullscreen mode

// app/robots.ts
import type { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: "*",
      allow: "/",
    },
    sitemap: "https://abosi.vercel.app/sitemap.xml",
  };
}

Enter fullscreen mode Exit fullscreen mode

Submit the sitemap URL (https://yoursite.com/sitemap.xml) to Google Search Console. This tells Google exactly which pages exist and how important they are relative to each other.


Step 6: Core Web Vitals — The Score That Actually Matters

Metadata and structured data tell Google what your site is. Core Web Vitals tell Google how good the experience is. Both feed into rankings.

The three metrics:

Metric What it measures Target
LCP How fast the main content loads < 2.5s
CLS How much the layout shifts < 0.1
INP Responsiveness to interaction < 200ms

What I did to improve them:

LCP: Added priority prop to the hero image in Next.js <Image />. This triggers a preload link in the HTML and makes the image load immediately instead of waiting for JavaScript.

<Image
  src="/profile.jpg"
  alt="Abosi Godwin"
  width={400}
  height={400}
  priority // ← this one line shaved 800ms off my LCP
/>

Enter fullscreen mode Exit fullscreen mode

CLS: Made sure every <Image /> had explicit width and height props so the browser reserves space before the image loads. Without this, images load and push content down — Google penalises that.

Fonts: Used next/font with display: swap to prevent invisible text during font load.

import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
});

Enter fullscreen mode Exit fullscreen mode


The Results

After implementing all of this and waiting about 3 weeks for Google to re-crawl:

  • My portfolio now appears on the first page for "Next.js developer Nigeria" and variations
  • Google Search Console shows impressions for "hire React developer Asaba" and "full stack developer Delta State"
  • The OG image shows correctly on every platform — LinkedIn, Twitter, WhatsApp

I'm not ranking for "Next.js developer" globally — I don't need to. I'm ranking for the searches that my actual clients make.


The Full Checklist

If you want to run through your own portfolio right now, here's everything in one place:

  • [ ] Unique title and description metadata on every page
  • [ ] openGraph and twitter metadata with an absolute OG image URL
  • [ ] JSON-LD Person schema on the homepage
  • [ ] Server component shell + client component split for pages that need both metadata and interactivity
  • [ ] sitemap.ts generated and submitted to Google Search Console
  • [ ] robots.ts configured
  • [ ] priority prop on your hero/above-fold image
  • [ ] Explicit width and height on every <Image />
  • [ ] next/font with display: swap
  • [ ] Verified with Google Rich Results Test and PageSpeed Insights

Final Thought

Your portfolio is not just a showcase. It is a landing page for your freelance business. Treat it like one.

Every founder who finds you through Google already has intent. They're not scrolling a feed and half-paying attention — they searched for someone like you. That's the best kind of lead. Make sure your site is the one they land on.


I'm Abosi Godwin — a full-stack developer based in Asaba, Nigeria. I build with Next.js, React, Supabase, and Tailwind CSS. If you found this useful, I write more practical content like this for developers and Nigerian founders. Follow me here on dev.to.

Want to work together? → abosi.vercel.app