MS

0
Skip to main content

How I Built manjodh.org — A Next.js Portfolio with Perfect SEO Scores

March 5, 2025 (1y ago)

Every developer needs a portfolio. But most developer portfolios are built as weekend projects and then forgotten — no SEO, no structured data, no performance optimization, and no content strategy. They sit on a subdomain somewhere, loading slowly, invisible to Google, and doing nothing for the developer's career.

I decided to build manjodh.org differently. My goal was not just a pretty page with my projects — it was a Next.js static site that ranks in search, loads instantly, and serves as a genuine platform for building professional authority. This post covers exactly how I built it, the SEO decisions I made, and the technical architecture behind a Next.js portfolio that actually performs.

I am Manjodh Singh Saran — a full stack developer based in Ludhiana, India. I work as a Senior Software Engineer and Mobile Lead at Truxo.ai, and I have been building web and mobile applications for 4.5 years. This portfolio is both my professional home and an ongoing experiment in developer-focused SEO.

Why Next.js for a Portfolio Site

The first decision was the framework. I chose Next.js 14 with the App Router and static export (output: "export") for several reasons:

Static HTML Generation

For a portfolio and blog, you do not need a server. Static HTML means:

Next.js output: "export" generates a complete static site at build time. Every page becomes an HTML file. Every blog post is pre-rendered. The entire site can be served from a CDN edge node closest to the visitor.

App Router Architecture

The App Router gives me file-system based routing with React Server Components by default. My directory structure is clean:

src/app/
├── page.tsx          # Home page
├── layout.tsx        # Root layout with metadata
├── blog/
│   ├── page.tsx      # Blog listing
│   └── [slug]/
│       └── page.tsx  # Individual blog posts

Each page can export its own metadata, which Next.js uses to generate the correct <head> tags. This is critical for SEO — each page gets unique titles, descriptions, and Open Graph tags without any manual HTML manipulation.

React Server Components

Server Components are the default in the App Router. They render on the server (or at build time for static export) and send zero JavaScript to the client. This means my portfolio's HTML shell — the layout, navigation, project cards, and content — adds zero bytes to the client-side JavaScript bundle.

I only use Client Components (marked with "use client") for genuinely interactive elements: the theme toggle, animation triggers, and the mobile navigation menu. Everything else stays as a Server Component.

SEO Architecture: The Technical Details

SEO for a developer portfolio is not about gaming Google. It is about making your content discoverable, properly structured, and fast. Here is every SEO element I implemented.

Meta Tags and Open Graph

Every page on manjodh.org has comprehensive meta tags generated by Next.js metadata API:

export const metadata: Metadata = {
  title: "Manjodh Singh Saran - Full Stack Developer",
  description: "Full Stack Software Engineer with 4.5 years of experience...",
  openGraph: {
    title: "Manjodh Singh Saran - Full Stack Developer",
    description: "Full Stack Software Engineer...",
    url: "https://manjodh.org",
    siteName: "Manjodh Singh Saran",
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "Manjodh Singh Saran - Full Stack Developer",
    description: "Full Stack Software Engineer...",
  },
};

For blog posts, the metadata is generated dynamically from the MDX frontmatter. Each post gets its own title, description, and publication date in the meta tags. This ensures that when someone shares a blog post on LinkedIn or Twitter, it displays with the correct title and description.

Structured Data (JSON-LD)

Structured data is what helps Google understand the content of your pages beyond the visible text. I implemented JSON-LD schemas for:

Person schema on the homepage:

{
  "@context": "https://schema.org",
  "@type": "Person",
  "name": "Manjodh Singh Saran",
  "jobTitle": "Senior Software Engineer",
  "url": "https://manjodh.org",
  "sameAs": [
    "https://github.com/ManjodhSaran",
    "https://linkedin.com/in/manjodh"
  ]
}

BlogPosting schema on each blog post:

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Post Title",
  "datePublished": "2025-03-05",
  "author": {
    "@type": "Person",
    "name": "Manjodh Singh Saran"
  }
}

These schemas help Google display rich results — author information, publication dates, and other enhanced search features.

Sitemap and Robots.txt

A sitemap tells search engines about every page on your site and when it was last updated. Next.js makes this straightforward with a sitemap.ts file:

export default function sitemap(): MetadataRoute.Sitemap {
  const posts = getBlogPosts().map((post) => ({
    url: `https://manjodh.org/blog/${post.slug}`,
    lastModified: post.metadata.publishedAt,
  }));
 
  return [
    { url: "https://manjodh.org", lastModified: new Date() },
    { url: "https://manjodh.org/blog", lastModified: new Date() },
    ...posts,
  ];
}

The robots.txt allows all crawlers and points them to the sitemap:

User-agent: *
Allow: /
Sitemap: https://manjodh.org/sitemap.xml

Canonical URLs

Every page has a canonical URL to prevent duplicate content issues. This is especially important if your site is accessible via both www and non-www versions, or if you syndicate content elsewhere.

Internal Linking Strategy

Internal links are one of the most effective and underused SEO techniques for developer portfolios. Every blog post on my site links to:

This is not random link stuffing. Each link adds genuine value for the reader. If I mention TypeScript in a blog post, I link to my TypeScript full stack best practices guide. If I discuss career advice, I link to relevant posts about becoming a full stack developer. This cross-linking builds topical authority — Google sees that my site covers topics comprehensively and interconnectedly.

Performance Optimization

Google has been clear that page speed is a ranking factor. Core Web Vitals — Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS) — directly impact search rankings.

Static Export Performance Wins

Because manjodh.org is a fully static site, performance is inherently excellent:

Image Optimization

Images are the most common performance bottleneck on portfolio sites. My approach:

  1. Compressed assets — All images are optimized before being added to the repository
  2. Proper dimensions — Images specify width and height to prevent layout shifts
  3. Lazy loading — Below-the-fold images load only when they enter the viewport
  4. Modern formats — WebP where possible for smaller file sizes

Font Loading Strategy

Custom fonts can cause layout shifts and slow down rendering. I use next/font to load fonts with the display: swap strategy, which shows a system font immediately and swaps to the custom font once it loads. This prevents invisible text during font loading.

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

Dark Mode Implementation

Dark mode is not just a nice-to-have — it reduces eye strain and many developers prefer it. My implementation uses the class strategy with Tailwind CSS:

  1. A theme provider detects the user's system preference
  2. A toggle button lets users override the system preference
  3. The preference is persisted in localStorage
  4. Tailwind's dark: variants handle all styling

The key technical detail is preventing flash of wrong theme. By inlining a small script in the <head> that reads localStorage before the page renders, the correct theme is applied immediately — no flash of white background for dark mode users.

// Theme is applied before React hydrates
<script dangerouslySetInnerHTML={{
  __html: `
    try {
      const theme = localStorage.getItem('theme') ||
        (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
      document.documentElement.classList.toggle('dark', theme === 'dark');
    } catch(e) {}
  `
}} />

Animation Architecture

Animations on a portfolio site should enhance, not distract. I built a BlurFade component using Framer Motion that creates a subtle blur-to-clear entrance animation:

const BlurFade = ({ children, delay = 0 }) => (
  <motion.div
    initial={{ opacity: 0, filter: "blur(10px)", y: 10 }}
    animate={{ opacity: 1, filter: "blur(0px)", y: 0 }}
    transition={{ duration: 0.4, delay }}
  >
    {children}
  </motion.div>
);

The staggered delay pattern (BLUR_FADE_DELAY * index) creates a cascading entrance effect where elements appear sequentially. This guides the visitor's eye through the content naturally.

Critically, these animations only run on initial page load. They do not re-trigger on every scroll or interaction — that would be annoying and hurt perceived performance.

Blog Architecture with MDX

The blog is a core part of the SEO strategy. Each post is written in MDX (Markdown with JSX support) with frontmatter metadata:

---
title: "Post Title"
publishedAt: "2025-03-05"
summary: "One-line summary for meta description"
---
 
Content goes here with **markdown** support.

Content Processing Pipeline

Blog posts go through a processing pipeline:

  1. gray-matter parses the frontmatter metadata
  2. remark processes the markdown content
  3. rehype-pretty-code adds syntax highlighting to code blocks using Shiki
  4. The processed content is rendered as React components

This pipeline runs at build time, so the rendered HTML is pre-generated. Visitors get instant page loads with beautifully highlighted code blocks — no client-side processing required.

Blog Listing and Sorting

The blog listing page fetches all MDX files from the content directory, sorts them by publication date, and renders them as cards with titles and summaries. Each card links to the full post using Next.js Link for client-side navigation.

Tailwind CSS and the Design System

The entire site uses Tailwind CSS with CSS custom properties for theming. Colors are defined as HSL values in globals.css:

:root {
  --background: 0 0% 100%;
  --foreground: 222 84% 5%;
  --primary: 222 47% 11%;
  /* ... */
}
 
.dark {
  --background: 222 84% 5%;
  --foreground: 210 40% 98%;
  --primary: 210 40% 98%;
  /* ... */
}

This approach means I can change the entire color scheme by modifying a few CSS variables. The cn() utility function — combining clsx and tailwind-merge — ensures className merging works correctly even with conditional and conflicting classes.

UI components follow the shadcn/ui pattern using Class Variance Authority (CVA) for type-safe variants. This makes components like buttons and badges consistent and maintainable.

Deployment and Hosting

The site is deployed on Vercel with automatic builds triggered by Git pushes. The deployment pipeline is:

  1. Push to main branch
  2. Vercel detects the push and triggers a build
  3. next build generates the static export
  4. Static files are distributed to Vercel's global CDN
  5. Site is live with SSL, HTTP/2, and edge caching

Build times are under 30 seconds. Deployments are atomic — the new version replaces the old one instantly with zero downtime. Preview deployments are generated for pull requests, making it easy to review changes before merging.

Content Strategy for Developer SEO

Having a technically excellent site is necessary but not sufficient. The content strategy is what drives organic traffic over time.

Topical Authority

My blog focuses on a cluster of related topics: full stack development, TypeScript, React Native, mobile development, and software engineering careers. By publishing multiple posts within these topic clusters and cross-linking them, I build topical authority — Google recognizes that manjodh.org is a comprehensive resource on these subjects.

If you are thinking about your own content strategy, you might find these posts valuable for understanding how deep technical content builds authority:

Writing for Developers

Developer content has specific requirements:

Connecting Your Portfolio to Business Outcomes

A portfolio is not just a vanity project. It can drive real business outcomes:

Every piece of content on the site serves a purpose in this funnel. The blog attracts visitors. The portfolio showcases capability. The service pages convert interest into action.

Lessons Learned

Building and maintaining this portfolio has taught me several things:

Start simple, iterate. The first version of manjodh.org was basic — just a landing page with projects. The blog, SEO optimization, and service pages came later. Do not wait for perfection to launch.

Consistency beats volume. Publishing one quality post per month is more valuable than publishing ten mediocre posts in a burst and then going silent for six months.

Technical SEO is a multiplier. All the content in the world will not rank if your site is slow, missing structured data, or not indexed properly. The technical foundation amplifies everything you build on top of it.

Measure what matters. Google Search Console shows you which queries bring visitors, which pages rank, and where you are losing clicks. Use that data to guide your content decisions.

What I Would Do Differently

If I were starting this portfolio from scratch today:

  1. Set up analytics and Search Console from day one. I waited too long to start tracking data.
  2. Plan the content calendar before building. Knowing what I would write about would have influenced the site architecture.
  3. Invest in Open Graph images earlier. Custom OG images for each blog post significantly improve click-through rates when shared on social media.
  4. Add an email capture from the start. Building an audience you own (email list) is more valuable than any social media following.

Get Started with Your Own Portfolio

If this post has inspired you to build your own developer portfolio, here is my practical advice:

  1. Use Next.js with static export. The developer experience is excellent and the performance is unbeatable for content sites.
  2. Set up proper SEO from the start. Meta tags, sitemap, robots.txt, and JSON-LD. Do not bolt these on later.
  3. Start writing immediately. Your first posts will not be great. Publish them anyway. You improve by doing.
  4. Cross-link everything. Every new post should link to at least 2-3 existing posts and pages.
  5. Deploy on a custom domain. yourname.com is worth the $12/year investment.

For more technical guides and career advice, check out more articles on my blog. And if you want to see all of this in action, explore my portfolio.

Building a portfolio is not a one-time project. It is an ongoing investment in your career. Make it count.

Related Articles

MS
Manjodh Singh Saran

Full Stack Developer · Ludhiana, India

Read more articles · View portfolio