Ayush Verma Portfolio (This Website)

Production-grade portfolio with an AI assistant + SEO hardening

Role

Creator / Full-Stack Engineer

Period

2025

Category

full stack

Overview

A Next.js portfolio with an in-site assistant (rule-based + optional FastAPI RAG backend), navigation-safe UX, and production-grade SEO/social/crawl hardening.

Key Highlights

  • Fully glassmorphic UI with standardized brand, layout, and component cards
  • AI assistant with navigation discovery (teaches + offers buttons, no surprise redirects)
  • Robust animations including tickers, orbits, and scroll-triggered reveals
  • SEO + structured data designed for reliable rich results

Tech Stack

Next.js (App Router)
TypeScript
Tailwind CSS (shadcn/ui)
Framer Motion
FastAPI (optional backend)
RAG retrieval (optional)

What I Learned

Case study: one JSON-LD "graph injector" (rich results without duplicate contexts)

Problem: Next.js makes it easy to add schema.org JSON-LD per page, but it’s also easy to accidentally emit multiple "@context" blocks (or emit nested @context inside nested entities). That can create noisy pages, flaky validators, and inconsistent rich-result parsing.

Solution: I built a tiny "JSON-LD graph injector" component that:

  • Accepts either a single schema object or an array.
  • Strips any nested "@context" from individual nodes.
  • Emits one canonical "@context" and a single "@graph" payload.

This keeps the head clean, keeps schema composable, and avoids subtle structured-data issues when composing Organization + WebSite + WebPage + Breadcrumbs + FAQ on the same route.

type JsonLdProps = {
  data: Record<string, unknown> | Array<Record<string, unknown>>;
};

export function JsonLd({ data }: JsonLdProps) {
  const payload = Array.isArray(data) ? data : [data];
  const graphNodes = payload.map((node) => {
    const { "@context": _ctx, ...rest } = node as Record<string, unknown>;
    return rest;
  });

  const json =
    graphNodes.length === 1
      ? { "@context": "https://schema.org", ...graphNodes[0] }
      : { "@context": "https://schema.org", "@graph": graphNodes };

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

Why it matters: it’s a small piece of code, but it prevents a whole class of "it validates on one page but not another" SEO issues — and it scales cleanly as the site grows.

Chat on WhatsApp