<?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: Harshikhaa Dass</title>
    <description>The latest articles on DEV Community by Harshikhaa Dass (@harshi_dhamu_b092c693795d).</description>
    <link>https://dev.to/harshi_dhamu_b092c693795d</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%2F4009941%2Fabd8d82f-7802-4432-a91f-a662013bb8dd.jpg</url>
      <title>DEV Community: Harshikhaa Dass</title>
      <link>https://dev.to/harshi_dhamu_b092c693795d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harshi_dhamu_b092c693795d"/>
    <language>en</language>
    <item>
      <title>I Built a Portfolio That Updates Itself When I Push to GitHub</title>
      <dc:creator>Harshikhaa Dass</dc:creator>
      <pubDate>Tue, 30 Jun 2026 17:32:54 +0000</pubDate>
      <link>https://dev.to/harshi_dhamu_b092c693795d/i-built-a-portfolio-that-updates-itself-when-i-push-to-github-3a2l</link>
      <guid>https://dev.to/harshi_dhamu_b092c693795d/i-built-a-portfolio-that-updates-itself-when-i-push-to-github-3a2l</guid>
      <description>&lt;p&gt;Every developer portfolio has the same problem: you build it once, get excited, and then it slowly goes stale. You ship five new projects over the next year and your portfolio still shows the same three repos from launch day.&lt;/p&gt;

&lt;p&gt;So I built one that fixes itself. Push a new repo to GitHub, and it shows up on my portfolio automatically — with an AI-written description, not just the raw GitHub blurb. Connect LinkedIn, and my experience and education populate themselves too.&lt;/p&gt;

&lt;p&gt;This is the full breakdown of how it works, why I made the choices I did, and how you can build your own version.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with every portfolio I'd seen
&lt;/h2&gt;

&lt;p&gt;Most portfolio templates fall into one of two categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Static templates&lt;/strong&gt; — you hardcode your projects in a JSON file or directly in JSX. The moment you ship something new, you're back in your code editor updating an array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub-pulling tools that already exist&lt;/strong&gt; — a few projects auto-list your repos, but they just dump the raw GitHub description with zero polish. No context, no tech stack analysis, nothing that actually sells the project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted something in between: automatic, but smart enough to write a good description on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The core idea is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub API → fetch your repos + READMEs
       ↓
AI (Groq/Llama) → reads each README → writes a clean 2-sentence summary
       ↓
Next.js → renders it all as a styled portfolio page
       ↓
Vercel → redeploys automatically whenever you push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing here needs a database. The portfolio fetches fresh data from GitHub on every page load (cached for an hour via Next.js's &lt;code&gt;revalidate&lt;/code&gt;), so there's no sync job, no cron, no webhook server to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Pulling GitHub data
&lt;/h2&gt;

&lt;p&gt;The GitHub REST API doesn't require OAuth for public repo data — a personal access token with &lt;code&gt;public_repo&lt;/code&gt; scope is enough:&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;res&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;`https://api.github.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_USERNAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/repos?sort=updated`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// refetch every hour&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;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one call gives you repo name, description, language, stars, forks, and the URL. The &lt;code&gt;revalidate&lt;/code&gt; option is doing a lot of work here — Next.js's App Router caches this fetch at the edge and automatically refreshes it every hour, so the page stays fast without needing a separate sync process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Making the AI write better descriptions than GitHub does
&lt;/h2&gt;

&lt;p&gt;GitHub's repo &lt;code&gt;description&lt;/code&gt; field is whatever one-liner you typed when you created the repo — often outdated or just "my project lol." I wanted the portfolio to actually explain what each project does.&lt;/p&gt;

&lt;p&gt;For every repo, I fetch the raw README and feed it to an LLM:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAIDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&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;readmeRes&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;`https://api.github.com/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/readme`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/vnd.github.raw&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;readme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;readmeRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;completion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;groq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llama-3.3-70b-versatile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Write a clear, impressive 2-sentence portfolio description for this GitHub project.

Repo: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Language: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
README: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;readme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;

Only output the description.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&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;completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&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;h3&gt;
  
  
  Why Groq instead of OpenAI or Claude
&lt;/h3&gt;

&lt;p&gt;I tried a few providers before settling on Groq:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Free Tier&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Why I didn't use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;Trial credits only&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Needs a card eventually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic Claude&lt;/td&gt;
&lt;td&gt;$5 credit on signup&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Same — eventually needs billing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Model names change constantly; kept hitting 404s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Groq (Llama 3.3 70B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fully free, no card&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fastest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;This is what I shipped with&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Groq's free tier is genuinely free — no credit card required at signup, and the inference speed is the fastest I've used for a hosted LLM. For a task like "summarize this README in two sentences," you don't need a frontier model — Llama 3.3 70B handles it well, and the response comes back in well under a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — LinkedIn sync without touching LinkedIn's API
&lt;/h2&gt;

&lt;p&gt;This was the trickiest part. LinkedIn's official API is locked down — you need partner access to read profile data programmatically, which isn't realistic for a side project.&lt;/p&gt;

&lt;p&gt;The workaround: LinkedIn lets every user &lt;strong&gt;export their own data&lt;/strong&gt; as a ZIP file from their account settings. Inside that ZIP are CSV files — &lt;code&gt;Positions.csv&lt;/code&gt;, &lt;code&gt;Education.csv&lt;/code&gt;, &lt;code&gt;Skills.csv&lt;/code&gt; — containing your full work history.&lt;/p&gt;

&lt;p&gt;So instead of an API integration, I built a simple upload flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User exports their LinkedIn data (official LinkedIn feature)
       ↓
Uploads the ZIP to my site
       ↓
Server unzips it, parses the CSVs
       ↓
Saves structured JSON
       ↓
Portfolio reads the JSON and renders it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parsing itself is straightforward once you have the ZIP open:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;JSZip&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jszip&lt;/span&gt;&lt;span class="dl"&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;zip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;JSZip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&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;posFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Positions.csv&lt;/span&gt;&lt;span class="dl"&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;posFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// simple CSV → array of objects&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;experience&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Company Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Started On&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Finished On&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Present&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No OAuth, no API keys, no rate limits — and it's fully compliant since you're only ever handling your own exported data, through LinkedIn's own official export tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — The design
&lt;/h2&gt;

&lt;p&gt;Functionally complete is one thing; a portfolio also has to look like something people remember. I went with a cyberpunk/glassmorphism aesthetic — dark background, glowing purple-to-cyan gradients, a particle network animation in the background, and an anime-style character illustration in the hero section.&lt;/p&gt;

&lt;p&gt;A few details that made it feel less like a template:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live clock&lt;/strong&gt; in the nav bar, just for the "this is a system" feel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor glow&lt;/strong&gt; that follows your mouse using a radial gradient positioned via &lt;code&gt;mousemove&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas-based particle network&lt;/strong&gt; — 80 small particles connected by faint lines when they're close to each other, redrawn every frame&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glassmorphism cards&lt;/strong&gt; — &lt;code&gt;backdrop-filter: blur(12px)&lt;/code&gt; with low-opacity backgrounds, so content behind them softly bleeds through&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is complicated individually, but together it makes the page feel alive instead of static.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently next time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache AI descriptions&lt;/strong&gt; — right now every page load regenerates descriptions for repos that haven't changed. Storing them keyed by the repo's last-updated timestamp would cut down on unnecessary API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a webhook instead of polling&lt;/strong&gt; — using GitHub's webhook system to trigger an instant rebuild on push, rather than relying on the hourly &lt;code&gt;revalidate&lt;/code&gt; window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support more LinkedIn fields&lt;/strong&gt; — certifications and recommendations are parsed but not yet displayed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;The whole project is open source. Clone it, drop in your own GitHub token and Groq API key, and you have a self-updating portfolio in about ten minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Harshi-dhamu/auto-portfolio.git
&lt;span class="nb"&gt;cd &lt;/span&gt;auto-portfolio
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full setup instructions, environment variables, and the AI provider comparison table are in the &lt;a href="https://github.com/Harshi-dhamu/auto-portfolio" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you build on top of it or have ideas for what's missing, I'd genuinely like to hear about it — PRs and issues are open.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
