<?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: Pratik Goswami</title>
    <description>The latest articles on DEV Community by Pratik Goswami (@pratik_goswami).</description>
    <link>https://dev.to/pratik_goswami</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3921707%2Ff3f6b28d-d101-43e7-a345-cd6af573a385.jpg</url>
      <title>DEV Community: Pratik Goswami</title>
      <link>https://dev.to/pratik_goswami</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pratik_goswami"/>
    <language>en</language>
    <item>
      <title>Part I: Don't Wait for Data. Render What You Know.</title>
      <dc:creator>Pratik Goswami</dc:creator>
      <pubDate>Tue, 19 May 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/pratik_goswami/part-i-dont-wait-for-data-render-what-you-know-4gaa</link>
      <guid>https://dev.to/pratik_goswami/part-i-dont-wait-for-data-render-what-you-know-4gaa</guid>
      <description>&lt;p&gt;&lt;em&gt;Part I of the series "Don't Let the User Wait - Render Quickly to Keep Their Attention."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem hiding inside SSR
&lt;/h2&gt;

&lt;p&gt;Server-Side Rendering promises the world: fast first paint, SEO-friendly markup, no content flash. For simple pages, it delivers. But real product pages are rarely simple. They need authentication checks, user details and live data to name a few - a cascade of API calls that all have to resolve before the server can respond.&lt;/p&gt;

&lt;p&gt;If the page only renders after all data are fetched, the server is just the new bottleneck and the user experience is hurt.&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%2F0edip2hf4ah4ihf91sst.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%2F0edip2hf4ah4ihf91sst.png" alt="Problem: Page is only rendered after Auth is checked and User Details and Chats are fetched" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets consider the case of a chat based application. The page needs to check user authentication before calling for the user details and user chats. All three API calls - auth, user details, chats - must complete before a single pixel is painted. Meanwhile, the user only sees a white screen and their experience is indistinguishable from a slow client-side app, regardless of whether the work is happening on the server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We moved to SSR and our Lighthouse score went up, but users still complain it feels slow."&lt;/em&gt; - every other engineering team.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The fix: Render the shell, let components own their data
&lt;/h2&gt;

&lt;p&gt;The key insight is that not all data is equally critical for the initial render. In the case depicted by the image above, &lt;em&gt;auth&lt;/em&gt; is critical to determine what the user is shown - you need a valid session before you send anything. But &lt;em&gt;userDetails&lt;/em&gt; and &lt;em&gt;chats&lt;/em&gt;? Those are content. The page can exist without them, and they can load in after the shell is already painted.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This article explores this using React and Next.js.&lt;/strong&gt; If you're working with a different stack, the same principle applies at the BFF layer - framework-agnostic and without any dependency on Server Components. That's covered in &lt;strong&gt;Part II - coming 27 May.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://react.dev/reference/rsc/server-components" rel="noopener noreferrer"&gt;&lt;strong&gt;React Server Components&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://react.dev/reference/react/Suspense" rel="noopener noreferrer"&gt;&lt;strong&gt;Suspense&lt;/strong&gt;&lt;/a&gt; make this the natural way to build. The idea splits into two clear phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 - Render the shell immediately.&lt;/strong&gt; &lt;br&gt;
The page render is only blocked for auth. Once the session is confirmed, the HTML shell - with navigation, layout and skeleton placeholders - is sent to the browser and rendered. The user sees something real and fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 - Each component fetches its own data.&lt;/strong&gt; &lt;br&gt;
&lt;code&gt;UserDetails&lt;/code&gt; and &lt;code&gt;Chats&lt;/code&gt; are asynchronous Server Components wrapped in Suspense boundaries. They each  independently fetch their own data on the server and render their  resolved HTML into the page as they complete - no waiting for each  other, no coordinating through a parent.&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%2F3yppfyfcipp7mpqd9dw7.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%2F3yppfyfcipp7mpqd9dw7.png" alt="Solution: Shell Rendered after auth and components render as data is fetched" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The user lands on a page that is already painted - navigation is visible, layout is in place, skeleton loaders hold the space where content will appear. Then &lt;code&gt;UserDetails&lt;/code&gt; and &lt;code&gt;Chats&lt;/code&gt; swap in, each one independently, as soon as its data is ready. &lt;/p&gt;


&lt;h2&gt;
  
  
  Suspense as the rendering contract
&lt;/h2&gt;

&lt;p&gt;React Suspense is the mechanism that wires this process together. Each data-dependent section of the page is wrapped in a &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;  boundary with a skeleton fallback. React renders the skeleton  immediately and replaces it with the resolved content the moment the  component is ready to render.&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="c1"&gt;// app/home/page.tsx (Next.js App Router)&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;Suspense&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&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;UserDetailsSkeleton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChatsSkeleton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./skeletons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Only auth blocks here — this is the only awaited call&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSession&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;session&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&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;/* Renders immediately — no data dependency */&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;Header&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="si"&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="cm"&gt;/* Skeleton shown instantly, swapped when data arrives */&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;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&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;&lt;/span&gt;&lt;span class="nc"&gt;UserDetailsSkeleton&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;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserDetails&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="nc"&gt;Suspense&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;/* Resolves independently — doesn't wait for UserDetails */&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;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&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;&lt;/span&gt;&lt;span class="nc"&gt;ChatsSkeleton&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;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ChatList&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="nc"&gt;Suspense&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;main&lt;/span&gt;&lt;span class="p"&gt;&amp;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;p&gt;Each Suspense boundary is independently resolvable. &lt;code&gt;UserDetails&lt;/code&gt; can swap in before &lt;code&gt;ChatList&lt;/code&gt;  is ready and vice versa. The user sees a progressively enriched page  rather than a binary flip from white screen to full content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Each component owns its own fetch
&lt;/h3&gt;

&lt;p&gt;The other half of the pattern is that each async Server Component fetches only what it needs, independently. There's no parent component collecting all the data and passing it down:&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="c1"&gt;// components/UserDetails.tsx&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserDetails&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;userId&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/details`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProfileCard&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;details&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;}&lt;/span&gt;

&lt;span class="c1"&gt;// components/ChatList.tsx&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ChatList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;userId&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/chats`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ChatFeed&lt;/span&gt; &lt;span class="na"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;chats&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;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What React does under the hood:&lt;/strong&gt; &lt;br&gt;
&lt;code&gt;renderToPipeableStream&lt;/code&gt;  sends the HTML shell to the browser first. As each Suspense boundary  resolves, React emits an inline script chunk over the same open  connection. The browser swaps the skeleton for the real content without a  full page reload or a separate network request.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Don't forget the error boundary
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;Suspense&lt;/code&gt; handles the loading state, pair it with an &lt;code&gt;ErrorBoundary&lt;/code&gt; to handle failure. This ensures that a broken API call degrades at the component level rather than crashing the whole page:&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="na"&gt;fallback&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;&lt;/span&gt;&lt;span class="nc"&gt;ChatsErrorCard&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;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&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;&lt;/span&gt;&lt;span class="nc"&gt;ChatsSkeleton&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;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ChatList&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="nc"&gt;Suspense&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="nc"&gt;ErrorBoundary&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;Now if &lt;code&gt;ChatList&lt;/code&gt; fails - network timeout, downstream 500,  or anything else - the rest of the page is completely unaffected.  Failed components can be retried either manually or automatically using  exponential backoff strategy. Now the page has a significant improvement over the old SSR  model where a single API failure would crash the entire render.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this does to your metrics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Classic SSR — wait for all&lt;/th&gt;
&lt;th&gt;Server Components + Suspense&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auth + all API calls combined&lt;/td&gt;
&lt;td&gt;Auth only — often 80–90% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;After all data resolves&lt;/td&gt;
&lt;td&gt;Shell arrives immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tied to the slowest API call&lt;/td&gt;
&lt;td&gt;Each section loads as ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One failure crashes the page&lt;/td&gt;
&lt;td&gt;Per-section Error Boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data ownership&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Centralised in page component&lt;/td&gt;
&lt;td&gt;Each component owns its fetch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  When to apply this pattern
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your page has a mix of critical and non-critical data.&lt;/strong&gt; The pattern shines when there's a clear hierarchy - shell first, primary content second, secondary content third.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend calls have variable latency.&lt;/strong&gt; If auth is fast but recommendations or chat APIs are slow, don't hold up the whole page. Let the slow sections render independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're on Next.js App Router.&lt;/strong&gt; Pages Router with &lt;code&gt;getServerSideProps&lt;/code&gt; doesn't support this model - all data fetching is centralised and blocking. App Router's async Server Components and &lt;code&gt;loading.tsx&lt;/code&gt; files are designed precisely for this pattern.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The old SSR model treats the page as a single blocking unit - nothing goes to the browser until everything is ready. Server Components and Suspense treat the page as a composition of independently resolvable sections, each with its own data timeline.&lt;/p&gt;

&lt;p&gt;Block render only on auth, and render the shell immediately. Let each component own its data and render its content when ready. Pair every Suspense with an ErrorBoundary so failures stay local and can be retried.&lt;/p&gt;

&lt;p&gt;The result is a page that feels genuinely fast - not because you moved computation around, but because you changed &lt;em&gt;when the user sees something&lt;/em&gt;.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Not on React or Next.js?&lt;/strong&gt; &lt;br&gt;
The same principle applies - but the fix lives in the BFF, not the frontend framework. Part II covers the framework-agnostic approach using HTTP streaming. Works with Vue, Svelte, Angular, or anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part II - Don't Wait for APIs. Send What You Have. → Coming 27 May.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Portfolio Is Invisible. Here's How I Fixed Mine.</title>
      <dc:creator>Pratik Goswami</dc:creator>
      <pubDate>Tue, 12 May 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/pratik_goswami/your-portfolio-is-invisible-heres-how-i-fixed-mine-1fek</link>
      <guid>https://dev.to/pratik_goswami/your-portfolio-is-invisible-heres-how-i-fixed-mine-1fek</guid>
      <description>&lt;p&gt;Everyone is building. AI tools have made it trivially easy to spin up a beautiful portfolio or product site in an afternoon and deploy it to Vercel or Netlify before dinner. But here is the thing nobody talks about: &lt;strong&gt;Vercel gives you a URL. It does not give you an audience.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I learned this firsthand. I have had a personal website at &lt;a href="https://www.pratikgoswami.dev/" rel="noopener noreferrer"&gt;pratikgoswami.dev&lt;/a&gt; for years. It looked great - it had my projects, experience, contact details - but the only people who ever visited it were recruiters who already had my resume or connections who clicked through from my LinkedIn. The site was essentially a &lt;strong&gt;private document with a public URL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then, in late April 2026, I spent two days fixing that. Here is exactly what I did and what happened after.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, Understand Who You Are Actually Building For
&lt;/h2&gt;

&lt;p&gt;Most developers optimize their site for one audience: humans. But your site has two types of readers: humans and machines. And increasingly, it is the machines that decide whether the humans ever find you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SEO (Search Engine Optimization)&lt;/strong&gt; is about helping Search Engines (like Google) crawl, understand, and rank your content so people find you through search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AEO (Answer Engine Optimization)&lt;/strong&gt; is newer, and arguably more important today. It is about helping AI assistants like ChatGPT, Gemini, and Claude form a confident, accurate understanding of who you are, so they can mention you when someone asks &lt;em&gt;"who are good full-stack developers with fintech experience?"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it this way: &lt;strong&gt;SEO gets you on the map. AEO makes you a landmark.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With LLMs becoming the next search interface, both matter. I decided to tackle them together.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Baseline
&lt;/h2&gt;

&lt;p&gt;Before I made any changes, my Google Search Console data tells an honest story. The site had a tiny trickle of impressions starting in early April, the result of some rudimentary metadata I had set up previously. But it was flat, sparse, and averaging a position of 36+ in search results, effectively invisible.&lt;/p&gt;

&lt;p&gt;I deployed my SEO and AEO changes on &lt;strong&gt;April 28th, 2026&lt;/strong&gt;. By &lt;strong&gt;April 30th&lt;/strong&gt;, impressions spiked sharply. In the 12 days since, the site has accumulated &lt;strong&gt;52 impressions&lt;/strong&gt;, with 2 actual clicks and a 1.8% CTR. Small numbers, but the trajectory is the story. The spike is real and it came immediately after the changes.&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%2F1g4n0rgnrbyf7efu1t7z.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%2F1g4n0rgnrbyf7efu1t7z.png" alt="Impression &amp;amp; Click Performance on Google Search Console" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let me walk you through exactly what I changed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Traditional SEO - Making It Readable for Google
&lt;/h2&gt;

&lt;p&gt;My site is built with &lt;strong&gt;Next.js (App Router), TypeScript, and Sanity CMS&lt;/strong&gt;. It looked clean to any human visitor. To a crawler, it was a meaningless soup of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Semantic HTML Refactoring
&lt;/h3&gt;

&lt;p&gt;The simplest change with the highest impact. I replaced generic container divs with meaningful HTML elements.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"skills-section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skills-section-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Skills&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    {skillList.map(({title, skills}) =&amp;gt; (
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-container"&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"{title}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-item-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          {skills &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; skills.map((skill, index) =&amp;gt; (
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-item"&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"{`${title}-${index}`}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{skill}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          ))}
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    ))}
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"skills-section"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Skills"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skills-section-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Technical Skills&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skills-grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {skillList.map(({ title, skills }) =&amp;gt; (
      &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-container"&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"{title}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-item-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          {skills &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; skills.map((skill, index) =&amp;gt; (
            &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"skill-item"&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"{`${title}-${index}`}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{skill}&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
          ))}
        &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
    ))}
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does this matter? A crawler reads your HTML like an outline. &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; through &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; tags are chapter headings. &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; are meaningful containers. Without them, Googlebot sees a flat document with no hierarchy and has to guess what is important.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fixing Content Discoverability
&lt;/h3&gt;

&lt;p&gt;This one surprised me. I had project details that rendered conditionally, after a user interaction: a click to expand, a tab switch, a state toggle. It looked fine in the browser, but Googlebot crawls the &lt;strong&gt;initial DOM&lt;/strong&gt;, not the post-interaction state. If your content only renders after a click, it effectively does not exist to Google.&lt;/p&gt;

&lt;p&gt;I refactored these components to render all content in the initial HTML, using CSS to control visibility rather than React state.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Adding Guides for Crawlers
&lt;/h3&gt;

&lt;p&gt;I added &lt;code&gt;robots.ts&lt;/code&gt; and &lt;code&gt;sitemap.ts&lt;/code&gt; to my project - two small files that do an important job. &lt;code&gt;robots.ts&lt;/code&gt; tells crawlers which pages they are allowed to visit. &lt;code&gt;sitemap.ts&lt;/code&gt; hands them a complete map of every page on your site so nothing gets missed. Next.js App Router makes this clean.&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;// app/robots.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;robots&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Robots&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="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;disallow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Routes that you dont want Crawler to see&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.pratikgoswami.dev/sitemap.xml&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/sitemap.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sitemap&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.pratikgoswami.dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monthly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is submitting your sitemap in Google Search Console. That single action transforms your site from something Google might eventually find into something Google knows exists today.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Performance and Core Web Vitals
&lt;/h3&gt;

&lt;p&gt;Google uses Core Web Vitals, specifically &lt;strong&gt;LCP (Largest Contentful Paint)&lt;/strong&gt;, as a ranking signal. LCP measures how long it takes for the largest visible element on your page to load - the lower the number, the faster your site feels to a visitor. &lt;/p&gt;

&lt;p&gt;I had been importing FontAwesome icons as an external dependency, which added unnecessary weight to the bundle. I replaced the animated canvas background with a native Canvas API implementation, cutting the external dependency entirely and improving load performance.&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%2F4glw5i3hkfj5iiv5nmwt.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%2F4glw5i3hkfj5iiv5nmwt.png" alt="Core Web Vitals" width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: AEO - Making It Readable for AI
&lt;/h2&gt;

&lt;p&gt;Most developers fix their SEO and think the job is done. It is not. Search behavior has fundamentally shifted. People are using ChatGPT, Gemini, and Claude as search engines now, asking them to recommend developers, tools, and products. If an AI model does not know who you are, you are invisible to an entirely new class of search. AEO is how you fix that.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. JSON-LD Structured Data
&lt;/h3&gt;

&lt;p&gt;Think of JSON-LD as your verified ID card for the internet - it tells search engines which queries you are relevant to, and gives AI models the structured facts they need to represent you accurately.&lt;/p&gt;

&lt;p&gt;I added a &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; block to my site's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; using the &lt;strong&gt;Person&lt;/strong&gt; and &lt;strong&gt;SoftwareApplication&lt;/strong&gt; schemas from Schema.org. This gives AI models explicit, machine-readable facts rather than forcing them to infer who you are from your prose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Script&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"schema-person"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;
    &lt;span class="na"&gt;dangerouslySetInnerHTML=&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;
      &lt;span class="na"&gt;__html:&lt;/span&gt; &lt;span class="na"&gt;JSON.stringify&lt;/span&gt;&lt;span class="err"&gt;({&lt;/span&gt;
          &lt;span class="err"&gt;"@&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;https:&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="na"&gt;schema.org&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="err"&gt;"@&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Person&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="na"&gt;name:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Pratik&lt;/span&gt; &lt;span class="na"&gt;Goswami&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="na"&gt;jobTitle:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Frontend&lt;/span&gt; &lt;span class="na"&gt;Software&lt;/span&gt; &lt;span class="na"&gt;Engineer&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="na"&gt;url:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;https:&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="na"&gt;www.pratikgoswami.dev&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="na"&gt;sameAs:&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;
          &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;https:&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="na"&gt;www.linkedin.com&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;prtkgoswami&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;https:&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="na"&gt;github.com&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;prtkgoswami&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="err"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;worksFor:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
          &lt;span class="err"&gt;"@&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Organization&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="na"&gt;name:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;TikTok&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;IBM&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt;
          &lt;span class="err"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;description:&lt;/span&gt;
          &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Frontend&lt;/span&gt; &lt;span class="na"&gt;Engineer&lt;/span&gt; &lt;span class="na"&gt;specializing&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="na"&gt;React&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TypeScript&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;and&lt;/span&gt; &lt;span class="na"&gt;scalable&lt;/span&gt; &lt;span class="na"&gt;UI&lt;/span&gt; &lt;span class="na"&gt;systems.&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="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, an LLM has to guess who you are based on scattered text. With it, you are handing the model a verified fact sheet.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Triangle of Trust - Entity Linking via sameAs
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;sameAs&lt;/code&gt; property is subtle but powerful. By linking your personal domain to your LinkedIn and GitHub profiles, you are creating a &lt;strong&gt;web of verification&lt;/strong&gt; across multiple authoritative sources.&lt;/p&gt;

&lt;p&gt;LLMs build a confidence score around named entities. If "Pratik Goswami" appears on a personal website, a LinkedIn profile, and a GitHub account, and those sources all link to each other, the model develops a high-confidence entity. It knows who you are. Without these links, you are just an unnamed node in a vast graph.&lt;/p&gt;

&lt;p&gt;This is the triangle of trust: &lt;strong&gt;your domain, LinkedIn, and GitHub&lt;/strong&gt;. Each point of the triangle reinforces the others.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Writing for RAG - Declarative, Specific Descriptions
&lt;/h3&gt;

&lt;p&gt;Retrieval-Augmented Generation (RAG) is how many LLMs fetch context before answering. They pull relevant chunks of text from indexed sources. That means &lt;strong&gt;how you write about your projects matters&lt;/strong&gt; as much as the schema you add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vague (bad for RAG):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Passionate developer who loves building impactful things."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Specific (good for RAG):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Built Gears Connect, a B2B automotive parts marketplace using Next.js, PostgreSQL, and Stripe, enabling real-time inventory management for 50+ vendors."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second version is indexable. It is full of specific, searchable facts. An LLM retrieving context for a query like &lt;em&gt;"full-stack developers who have built marketplace apps"&lt;/em&gt; can actually use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. JSON-LD Over llms.txt
&lt;/h3&gt;

&lt;p&gt;You may have seen chatter about &lt;code&gt;llms.txt&lt;/code&gt;, a proposed standard for helping LLMs read your site, but it is a shortcut, not a foundation. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a plain-text file placed at the root of your domain that gives AI crawlers a human-readable summary of your site, similar to how &lt;code&gt;robots.txt&lt;/code&gt; works for search crawlers. Low effort to set up, but also low density. It carries no schema, no entity relationships, and zero influence over search rankings.&lt;/p&gt;

&lt;p&gt;JSON-LD does all of that and more. It is a W3C standard understood by Google, Bing, LinkedIn, Perplexity, and SearchGPT simultaneously. It defines explicit relationships - that "Pratik Goswami" is a &lt;code&gt;Person&lt;/code&gt;, &lt;code&gt;worksFor&lt;/code&gt; specific organizations, and is the &lt;code&gt;author&lt;/code&gt; of specific projects. It also unlocks Google Rich Results, putting your job title and links directly in the search listing. &lt;code&gt;llms.txt&lt;/code&gt; cannot do any of this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: Monitoring - Closing the Loop
&lt;/h2&gt;

&lt;p&gt;Shipping SEO and AEO changes without monitoring is like sending a message and never checking if it was delivered. Monitoring closes the feedback loop - it tells you what Google has indexed, which queries are surfacing your name, and which parts of your site are actually holding a visitor's attention. Without it, you are optimizing blind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Search Console&lt;/strong&gt; was the first thing I set up, before deploying any changes. Verifying your domain and submitting your sitemap establishes a direct line of communication with Google.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The LLM test&lt;/strong&gt; is simple and satisfying: ask ChatGPT, Gemini, and Claude "Who is Pratik Goswami?" My site now surfaces accurate, confident responses across all three. That is the AEO working.&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%2Fif2r9sd7u3feweytx8cs.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%2Fif2r9sd7u3feweytx8cs.png" alt="Test on Gemini &amp;amp; GPT" width="800" height="341"&gt;&lt;/a&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%2Fu68adlqvxf1q9gjdhry3.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%2Fu68adlqvxf1q9gjdhry3.png" alt="Google Search Test" width="800" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom Google Analytics tracking&lt;/strong&gt; using &lt;code&gt;IntersectionObserver&lt;/code&gt; lets me track which sections of my site recruiters actually read, not just that they visited. Knowing that the "&lt;a href="https://jobtrack.pratikgoswami.dev" rel="noopener noreferrer"&gt;Job Application Tracker&lt;/a&gt;" project card gets more dwell time than others is actionable data, not a vanity metric.&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;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&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="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entry&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sectionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// User entered a section&lt;/span&gt;
                &lt;span class="nx"&gt;activeSection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sectionId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nx"&gt;enterTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nf"&gt;sendSectionView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sectionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeSection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;sectionId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;enterTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// User left the section → calculate time spent&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;enterTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;sendTimeSpent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sectionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;activeSection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nx"&gt;enterTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Trigger when 50% of a section is visible&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;SECTIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&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;
  
  
  Was It Worth It?
&lt;/h2&gt;

&lt;p&gt;In two days of work, the site went from effectively invisible to ranking on the first page of Google for my name, accumulating 52 impressions in 12 days, and being recognized by ChatGPT, Gemini, and Claude when asked who I am.&lt;/p&gt;

&lt;p&gt;The numbers are early, but the direction is clear. The spike in the Google Search Console (GSC) chart is immediate and unambiguous.&lt;/p&gt;

&lt;p&gt;Here is what I want you to take away:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A beautiful site and a discoverable site are not the same thing&lt;/strong&gt;. Most portfolios are the former, not the latter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AEO is not the future. It is right now&lt;/strong&gt;. LLMs are already answering career questions. If you are not structured for them, you are invisible to them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The implementation is smaller than you think&lt;/strong&gt;. Two files, one schema block, and a few hours of refactoring later, my site went from invisible to indexed. The effort was minimal. The compounding visibility was not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat your portfolio like a product&lt;/strong&gt;. Ship, measure, iterate.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Your Quick-Start Checklist
&lt;/h2&gt;

&lt;p&gt;Pick one item from this list and do it this weekend:&lt;/p&gt;

&lt;p&gt;[ ] Replace &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; wrappers with &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;, and proper &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;–&lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; hierarchy&lt;br&gt;
[ ] Audit for content hidden behind React state and render it in the initial HTML&lt;br&gt;
[ ] Add &lt;code&gt;robots.ts&lt;/code&gt; and &lt;code&gt;sitemap.ts&lt;/code&gt; in Next.js App Router&lt;br&gt;
[ ] Submit your sitemap in Google Search Console&lt;br&gt;
[ ] Run PageSpeed Insights and check your LCP score&lt;br&gt;
[ ] Add a &lt;code&gt;Person&lt;/code&gt; JSON-LD schema to your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;&lt;br&gt;
[ ] Add &lt;code&gt;sameAs&lt;/code&gt; links to LinkedIn and GitHub in your schema&lt;br&gt;
[ ] Rewrite your project descriptions to be specific, declarative, and technical&lt;br&gt;
[ ] Ask ChatGPT or Gemini who you are and see what they say today&lt;/p&gt;




&lt;h2&gt;
  
  
  So… What Is Next?
&lt;/h2&gt;

&lt;p&gt;This is just the starting point. The real value of setting up monitoring is that the data now tells me where to focus next - which projects are getting attention, which queries are surfacing my name, and where there is still room to improve. I will keep the structured data updated as my experience grows, revisit the content as AEO standards evolve, and let the analytics guide the next round of changes. The foundation is built. Now it gets to compound.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
