<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prantik Ghosh</title>
    <description>The latest articles on DEV Community by Prantik Ghosh (@pr4x9s).</description>
    <link>https://dev.to/pr4x9s</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1791844%2F517ed08e-50be-4aed-9fcd-31dbf282eef1.jpeg</url>
      <title>DEV Community: Prantik Ghosh</title>
      <link>https://dev.to/pr4x9s</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pr4x9s"/>
    <language>en</language>
    <item>
      <title>Revamping My Portfolio - Markdown Engine, DOM Hydration Fixes, and LLM Discovery</title>
      <dc:creator>Prantik Ghosh</dc:creator>
      <pubDate>Fri, 09 Oct 2026 11:17:00 +0000</pubDate>
      <link>https://dev.to/pr4x9s/revamping-my-portfolio-markdown-engine-dom-hydration-fixes-and-llm-discovery-4af</link>
      <guid>https://dev.to/pr4x9s/revamping-my-portfolio-markdown-engine-dom-hydration-fixes-and-llm-discovery-4af</guid>
      <description>&lt;h2&gt;
  
  
  Why the Redesign?
&lt;/h2&gt;

&lt;p&gt;My previous portfolio had become outdated and didn't represent the quality or complexity of the systems I build today[cite: 3]. Beyond giving the UI a cleaner visual hierarchy and tighter typography[cite: 4], this rebuild was about making the platform functional: adding a dedicated technical blog, ensuring dark mode works across all dynamic media, and making the site discoverable by both search engines and AI models.&lt;/p&gt;

&lt;p&gt;Here are the key engineering updates and solutions implemented during the rebuild:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    subgraph Discovery ["Discovery Layer"]
        A["Googlebot / Crawlers"] --&amp;gt;|"Parses Graph"| B["JSON-LD Schema @id: #person"]
        C["AI Engines (Perplexity / ChatGPT)"] --&amp;gt;|"Fetches Summary"| D["/llms.txt Spec"]
    end

    subgraph ContentEngine ["Blog &amp;amp; Rendering Pipeline"]
        E["Vite glob (.md files)"] --&amp;gt; F["Custom blogLoader.ts (YAML Frontmatter)"]
        F --&amp;gt; G["Streamdown Engine"]
        G --&amp;gt; H["ZoomPanImage (createPortal Lightbox)"]
        G --&amp;gt; I["Mermaid Diagrams (Dark Mode Reactive)"]
    end

    B --&amp;gt; ContentEngine
    D --&amp;gt; ContentEngine&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  1. Custom Markdown Blog Engine with Resilient Error Handling
&lt;/h2&gt;

&lt;p&gt;Instead of relying on an external CMS, the blog runs directly off local Markdown files parsed at build/runtime using Vite's &lt;code&gt;import.meta.glob&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// blogLoader.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/src/content/blogs/*.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;?raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;import&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;eager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Preventing Empty File Crashes
&lt;/h3&gt;

&lt;p&gt;During development, creating a blank &lt;code&gt;.md&lt;/code&gt; draft or omitting frontmatter dividers caused &lt;code&gt;js-yaml&lt;/code&gt; to crash with &lt;code&gt;YAMLException: expected a document, but the input is empty&lt;/code&gt;. We hardened the loader with guarded string checks and fallback meta defaults so draft files fail silently instead of taking down the entire route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseYamlMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Solving React DOM Nesting &amp;amp; Hydration Bugs in Markdown
&lt;/h2&gt;

&lt;p&gt;When adding interactive image zoom (&lt;code&gt;ZoomPanImage&lt;/code&gt;) to Markdown posts, React began throwing hydration and nesting errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;In HTML, &amp;lt;figure&amp;gt; cannot be a descendant of &amp;lt;p&amp;gt;.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;In HTML, &amp;lt;div&amp;gt; cannot be a descendant of &amp;lt;p&amp;gt;.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because CommonMark parsers automatically wrap standalone images in &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags, rendering block-level wrappers (&lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;) causes browser HTML parsers to split the DOM, breaking React's hydration tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Swap outer wrappers to inline spans&lt;/strong&gt;: Styled with &lt;code&gt;block&lt;/code&gt; or &lt;code&gt;inline-block&lt;/code&gt;, &lt;span&gt; elements satisfy HTML phrasing content rules while keeping exact layout styling intact.&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mount the lightbox via &lt;code&gt;createPortal&lt;/code&gt;&lt;/strong&gt;: Teleporting the fullscreen zoom/pan modal directly to &lt;code&gt;document.body&lt;/code&gt; keeps it completely outside the article's &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; hierarchy and eliminates z-index fights with sticky headers:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside ZoomPanImage.tsx&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'my-6 block w-full text-center'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'inline-block cursor-zoom-in'&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'rounded-xl mx-auto'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'mt-2 block text-xs font-mono text-slate-400'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Fig. &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;createPortal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'fixed inset-0 z-50 flex items-center justify-center bg-slate-950/80 backdrop-blur-md'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Fullscreen Zoom &amp;amp; Pan Canvas */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Dark-Mode Compatible Mermaid Diagrams
&lt;/h2&gt;

&lt;p&gt;Standard Mermaid diagrams output SVG elements calculated with dark fills and strokes (&lt;code&gt;#333333&lt;/code&gt;) by default, making text unreadable against dark backgrounds.&lt;/p&gt;

&lt;p&gt;Because Mermaid compiles SVGs via its own JavaScript engine rather than CSS utility classes, we synchronized diagram rendering with our theme store by dynamically instantiating the plugin and keying the container on theme change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mermaidPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createMermaidPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isDarkMode&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDarkMode&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Streamdown&lt;/span&gt; &lt;span class="s"&gt;'dark'&lt;/span&gt; &lt;span class="s"&gt;'light'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="na"&gt;cjk&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{isDarkMode"&lt;/span&gt; &lt;span class="na"&gt;math&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mermaid&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="na"&gt;mermaidPlugin&lt;/span&gt; &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{{"&lt;/span&gt; &lt;span class="err"&gt;}}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawContent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Streamdown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing &lt;code&gt;key={isDarkMode ? 'dark' : 'light'}&lt;/code&gt; forces the container to invalidate the cached light SVG and freshly render crisp, readable dark-mode diagrams on toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Modern Discovery: Linked JSON-LD &amp;amp; &lt;code&gt;llms.txt&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For discoverability, the site uses a linked two-tier architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unified Schema Graph (&lt;code&gt;#person&lt;/code&gt;)&lt;/strong&gt;: Instead of repeating fragmented author fields across subpages, sub-routes (&lt;code&gt;/experience&lt;/code&gt;, &lt;code&gt;/projects&lt;/code&gt;, &lt;code&gt;/blogs&lt;/code&gt;) link their &lt;code&gt;mainEntity&lt;/code&gt; or &lt;code&gt;author&lt;/code&gt; back to a permanent URI anchor (&lt;code&gt;https://prantikghosh.vercel.app/#person&lt;/code&gt;). This prevents duplicate entity warnings in Google Search Console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/llms.txt&lt;/code&gt;&lt;/strong&gt;: Added an &lt;code&gt;/llms.txt&lt;/code&gt; manifest at the site root to give modern LLMs (Perplexity, ChatGPT, Gemini) clean, factual context about my skill set, background, and production work without relying on unpredictable web scraping.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;A portfolio redesign is a great opportunity to audit practical edge cases: handling invalid DOM nesting in Markdown content, building robust YAML loaders, and keeping SVG diagram renderers reactive to dark mode. The result is a faster, cleaner, and more resilient platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch Markdown DOM Nesting&lt;/strong&gt;: Use inline elements (&lt;code&gt;span&lt;/code&gt;) and &lt;code&gt;createPortal&lt;/code&gt; to prevent invalid block-element hydration mismatches inside &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guard Dynamic Loaders&lt;/strong&gt;: Always wrap frontmatter and YAML parsers in null-checks and &lt;code&gt;try/catch&lt;/code&gt; blocks to prevent blank draft files from breaking routes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sync Canvas &amp;amp; SVG Themes&lt;/strong&gt;: Rerender SVG generators like Mermaid with explicit keys so they track your app's theme state properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structure for AI Discovery&lt;/strong&gt;: Use &lt;code&gt;llms.txt&lt;/code&gt; alongside linked JSON-LD schemas so both search crawlers and AI answer engines index your information cleanly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>tailwindcss</category>
      <category>typescript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>What is Aggregation Pipeline in MongoDB?</title>
      <dc:creator>Prantik Ghosh</dc:creator>
      <pubDate>Sun, 14 Dec 2025 14:47:15 +0000</pubDate>
      <link>https://dev.to/pr4x9s/what-is-aggregation-pipeline-in-mongodb-5gfb</link>
      <guid>https://dev.to/pr4x9s/what-is-aggregation-pipeline-in-mongodb-5gfb</guid>
      <description>&lt;p&gt;&lt;strong&gt;Aggregation&lt;/strong&gt; in MongoDB is referred as the process of performing different types of operations on multiple documents within a collection. This is done by using pipelines, which is made up of different stages. One stage output is the input for another/next stage.&lt;/p&gt;

&lt;p&gt;Types of Aggregation pipelines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;$match&lt;/code&gt;: This is a fundamental pipeline stage used within the &lt;strong&gt;Aggregation&lt;/strong&gt; to &lt;u&gt;&lt;em&gt;filter&lt;/em&gt;&lt;/u&gt; documents. It functions similarly to the standard &lt;code&gt;find()&lt;/code&gt; query but is designed to work as a step in a &lt;strong&gt;multi-stage data processing pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const channel = await User.aggregate([
        {
            $match: {
                username: username?.toLowerCase()
            }
        },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;$lookup&lt;/code&gt;: This is an aggregation stage used to perform a &lt;strong&gt;left outer join&lt;/strong&gt; between two collections in the same database. It allows us to merge data from a &lt;code&gt;foreign&lt;/code&gt; collection into documents from a &lt;code&gt;local&lt;/code&gt; collection, enriching them with related information. The &lt;code&gt;$lookup&lt;/code&gt; passes the reshaped stage to next stage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        {
            $lookup: {
                from: 'subscriptions',
                localField: '_id',
                foreignField: 'channel',
                as: 'subscribers'
            }
        },
        {
            $lookup: {
                from: 'subscriptions',
                localField: '_id',
                foreignField: 'subscriber',
                as: 'subscribedTo'
            }
        },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;$addFields&lt;/code&gt;: This is an aggregation pipeline stage that adds &lt;strong&gt;new fields&lt;/strong&gt; to documents or &lt;strong&gt;overwrites existing ones&lt;/strong&gt;. It is particularly useful when we want to include all existing fields in the output while only modifying or adding a few specific ones.
Note: As per the docs, we can also use &lt;code&gt;$set&lt;/code&gt;, as &lt;code&gt;$set&lt;/code&gt; is an alias of &lt;code&gt;$addFields&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        {
            $addFields: {
                subscribersCount: {
                    $size: '$subscribers'
                },
                channelsSubscribedToCount: {
                    $size: '$subscribedTo'
                },
                isSubscribed: {
                    $cond: {
                        if: {$in: [req.user?._id, '$subscribers.subscribers']},
                        then: true,
                        else: false
                    }
                }
            }
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, &lt;code&gt;$cond&lt;/code&gt; is a ternary aggregation operator that allows us to implement &lt;code&gt;if-then-else&lt;/code&gt; logic within aggregation pipelines, projections, and updates. It evaluates a boolean expression and returns one of two specified values based on the result.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;$project&lt;/code&gt;: This is an aggregation pipeline stage that passes only specified fields to the next stage. It is used to:&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;include or exclude fields&lt;/em&gt;: 1(true) to include a field and 0(false) to exclude.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;create new fields&lt;/em&gt;: add computed fields or rename existing ones by assigning an expression to a new field name.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;reshape data&lt;/em&gt;: it can extract sub-fields from nested documents or create new array fields.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
            $project: {
                fullName: 1,
                username: 1,
                subscribersCount: 1,
                channelsSubscribedToCount: 1,
                isSubscribed: 1,
                avatar: 1,
                coverImage: 1,
                email: 1,
            }
        }
    ]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhb0tjvnty5h2on27975c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhb0tjvnty5h2on27975c.png" alt=" " width="800" height="1644"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mongodb</category>
      <category>express</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>🌦️ 𝗝𝘂𝘀𝘁 𝗯𝘂𝗶𝗹𝘁 𝗮 𝗺𝗶𝗻𝗶 𝗪𝗲𝗮𝘁𝗵𝗲𝗿 𝗔𝗽𝗽 𝘄𝗶𝘁𝗵 𝗥𝗲𝗮𝗰𝘁 + 𝗧𝗮𝗻𝗦𝘁𝗮𝗰𝗸 𝗤𝘂𝗲𝗿𝘆 – 𝗮𝗻𝗱 𝗶𝘁'𝘀 𝗮 𝗣𝗪𝗔! 📱💻</title>
      <dc:creator>Prantik Ghosh</dc:creator>
      <pubDate>Mon, 23 Jun 2025 19:32:21 +0000</pubDate>
      <link>https://dev.to/pr4x9s/--8ke</link>
      <guid>https://dev.to/pr4x9s/--8ke</guid>
      <description>&lt;p&gt;Wanted to explore efficient data fetching &amp;amp; caching with TanStack Query (React Query) made it smooth and powerful. 💡&lt;/p&gt;

&lt;p&gt;🔹 Real-time weather data&lt;br&gt;
🔹 Clean API handling&lt;br&gt;
🔹 Auto-detect your curent location and show real-time weather instantly — thanks to the 𝗚𝗲𝗼𝗹𝗼𝗰𝗮𝘁𝗶𝗼𝗻 𝗔𝗣𝗜 + 𝗧𝗮𝗻𝗦𝘁𝗮𝗰𝗸 𝗤𝘂𝗲𝗿𝘆! ⚡&lt;br&gt;
🔹 Built with &lt;strong&gt;React&lt;/strong&gt;, &lt;strong&gt;Tailwind CSS&lt;/strong&gt; &amp;amp; &lt;strong&gt;OpenWeatherMap API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;📌 𝗖𝗵𝗲𝗰𝗸 𝗶𝘁 𝗼𝘂𝘁:&lt;br&gt;
&amp;nbsp;&amp;nbsp;• &lt;a href="https://sunsyncs.vercel.app/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;• &lt;a href="https://github.com/TBS96/weather-app-react" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👨‍💻 𝗠𝗼𝗿𝗲 𝗣𝗿𝗼𝗷𝗲𝗰𝘁𝘀:&lt;br&gt;
&lt;a href="https://prantikghosh.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Always learning, always building. Feedback welcome! 🙌&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/feed/update/urn:li:share:7343640591601229824/" rel="noopener noreferrer"&gt;LinkedIn Post&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;#reactJS #TanStackQuery #ReactQuery #FrontendDev #JavaScript #WeatherApp&lt;/p&gt;

</description>
      <category>react</category>
      <category>pwa</category>
      <category>tanstackquery</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>𝗥𝗲𝗮𝗰𝘁’𝘀 `𝙠𝙚𝙮` 𝗽𝗿𝗼𝗽 𝗶𝘀 𝗼𝗻𝗲 𝗼𝗳 𝘁𝗵𝗲 𝗺𝗼𝘀𝘁 𝗺𝗶𝘀𝘂𝗻𝗱𝗲𝗿𝘀𝘁𝗼𝗼𝗱 𝘁𝗵𝗶𝗻𝗴𝘀 — 𝗮𝗻𝗱 𝘁𝗼𝗱𝗮𝘆 𝗜 𝗱𝗲𝗰𝗶𝗱𝗲𝗱 𝘁𝗼 𝗯𝗿𝗲𝗮𝗸 𝗶𝘁 𝗱𝗼𝘄𝗻 𝗶𝗻 𝘁𝗵𝗲 𝘀𝗶𝗺𝗽𝗹𝗲𝘀𝘁 𝗽𝗼𝘀𝘀𝗶𝗯𝗹𝗲 𝘄𝗮𝘆 👇</title>
      <dc:creator>Prantik Ghosh</dc:creator>
      <pubDate>Thu, 24 Apr 2025 14:56:02 +0000</pubDate>
      <link>https://dev.to/pr4x9s/--22ip</link>
      <guid>https://dev.to/pr4x9s/--22ip</guid>
      <description>&lt;h2&gt;
  
  
  🔑 What is &lt;code&gt;key&lt;/code&gt; in React?
&lt;/h2&gt;

&lt;p&gt;Whenever you render a list in React, you need to assign a &lt;code&gt;key&lt;/code&gt; to each item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const items = ['🚀', '🔥', '💻'];
items.map((item, index) =&amp;gt; (
  &amp;lt;li key={index}&amp;gt; {item} &amp;lt;/li&amp;gt;
));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, using the &lt;code&gt;index&lt;/code&gt; as the &lt;code&gt;key&lt;/code&gt; seems fine...&lt;/p&gt;

&lt;p&gt;But here’s the truth: &lt;strong&gt;this can lead to subtle UI bugs and performance issues&lt;/strong&gt; when items are added, removed, or reordered.&lt;/p&gt;




&lt;p&gt;💥 &lt;strong&gt;Why&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;React uses &lt;code&gt;key&lt;/code&gt; to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify which items have changed&lt;/li&gt;
&lt;li&gt;Reuse DOM elements efficiently&lt;/li&gt;
&lt;li&gt;Avoid unnecessary re-renders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When &lt;code&gt;keys&lt;/code&gt; are unstable (like shifting indexes), React gets &lt;u&gt;confused&lt;/u&gt; and might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update the wrong component&lt;/li&gt;
&lt;li&gt;Lose input focus&lt;/li&gt;
&lt;li&gt;Animate incorrectly&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;✅ &lt;strong&gt;The Better Way&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const items = [
  { id: 1, emoji: '🚀' },
  { id: 2, emoji: '🔥' },
  { id: 3, emoji: '💻' },
];

items.map(({id, emoji}) =&amp;gt; (
  &amp;lt;li key={id}&amp;gt;
     {emoji} {console.log(id)}
  &amp;lt;/li&amp;gt;
));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧠 This way, each &lt;code&gt;item&lt;/code&gt; has a &lt;strong&gt;stable identity&lt;/strong&gt;. React can now confidently handle changes without weird UI glitches.&lt;/p&gt;




&lt;p&gt;🚨 &lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Don’t just use &lt;code&gt;index&lt;/code&gt; for the sake of &lt;u&gt;removing a warning&lt;/u&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;unique and stable keys&lt;/strong&gt; to unlock React’s full rendering power.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This small habit will save you from big bugs later!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;💬 &lt;strong&gt;Did this clarify something for you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me know in the comments — or just drop a 🔥 if you’ve learned something new!&lt;/p&gt;

&lt;p&gt;Click &lt;a href="https://www.linkedin.com/posts/prantikghosh96_reactjs-javascript-webdevelopment-activity-7317240958251675648-zqI0?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAACUJKCQBrShLrjpmIiKPX0dmK3lHA1yrfwI" rel="noopener noreferrer"&gt;ME&lt;/a&gt; to get the original post on &lt;strong&gt;LinkedIn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjm9y31mgkx2s7st4zxe.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjm9y31mgkx2s7st4zxe.jpeg" alt="code" width="780" height="1086"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
