<?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: Ritika Rajput</title>
    <description>The latest articles on DEV Community by Ritika Rajput (@ritika_rajput).</description>
    <link>https://dev.to/ritika_rajput</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%2F4172631%2F243a0816-3723-4c79-812d-364c24150d2c.jpg</url>
      <title>DEV Community: Ritika Rajput</title>
      <link>https://dev.to/ritika_rajput</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ritika_rajput"/>
    <language>en</language>
    <item>
      <title>Making My Portfolio Less Boring: 4 Small Redesigns That Made a Big Difference</title>
      <dc:creator>Ritika Rajput</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:14:24 +0000</pubDate>
      <link>https://dev.to/ritika_rajput/making-my-portfolio-less-boring-4-small-redesigns-that-made-a-big-difference-52jk</link>
      <guid>https://dev.to/ritika_rajput/making-my-portfolio-less-boring-4-small-redesigns-that-made-a-big-difference-52jk</guid>
      <description>&lt;p&gt;When I first launched my portfolio, it worked, but it felt empty. Some pages had big text and lots of blank space. Some sections said "Coming soon". Two pages almost looked the same.&lt;/p&gt;

&lt;p&gt;I didn't rebuild anything. I made four small changes, and the site felt much more finished. Here they are, with examples you can copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Replace "Coming soon" with real work
&lt;/h2&gt;

&lt;p&gt;My Projects page ended with two cards that said &lt;strong&gt;"Coming soon"&lt;/strong&gt; and &lt;strong&gt;"A new piece is finding its final frame."&lt;/strong&gt; They looked unfinished, and they told visitors there was nothing there.&lt;/p&gt;

&lt;p&gt;I deleted them and used real things I had already built instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A side project (a Python script that publishes a blog post every day with GitHub Actions)&lt;/li&gt;
&lt;li&gt;My articles on dev.to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Placeholder cards don't help anyone. If you have nothing new to show, remove the section. If you have something real, even a small project or an article, show that.&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="nt"&gt;a&lt;/span&gt;
  &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://github.com/your-username/your-repo"&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;
  &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&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;"flex flex-col gap-3 rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-5 transition-colors hover:border-[var(--accent)]"&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;"text-xs uppercase tracking-wider text-[var(--text-muted)]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Project&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;h3&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;"text-lg font-semibold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Daily blog automation&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&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;p&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;"text-sm leading-6 text-[var(--text-muted)]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    A Python script that writes and publishes a blog post every day.
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&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;"mt-auto text-sm font-semibold text-[var(--accent)]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;View on GitHub →&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;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Use the empty space next to a big heading
&lt;/h2&gt;

&lt;p&gt;My Contact page had a huge heading on one side and nothing on the other. A visitor had to scroll to find out how to reach me.&lt;/p&gt;

&lt;p&gt;I moved the contact options &lt;strong&gt;next to&lt;/strong&gt; the heading as three clickable cards: Email, WhatsApp and LinkedIn. Under the heading, I added small chips for what I can help with.&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="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;"grid gap-8 lg:grid-cols-2 lg:items-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;div&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Let's make something good.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Have a project in mind? I'd love to hear what you're thinking.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&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;div&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;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;"flex flex-col gap-3"&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;/* three contact cards here */&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same idea worked on my Skills page: the empty right side now shows four small cards that link to the skill details below.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Turn a row of small links into cards
&lt;/h2&gt;

&lt;p&gt;My "Find me online" section was a row of tiny text chips. They were easy to miss, and a visitor couldn't tell what each link was.&lt;/p&gt;

&lt;p&gt;I changed them into larger cards with an icon, a name and one short line, such as "Articles on web development and automation". Now each link explains itself before anyone clicks it.&lt;/p&gt;

&lt;p&gt;Keep each card focused on one job: an icon, a title, one line of description and one clear action.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check your contrast
&lt;/h2&gt;

&lt;p&gt;My "Let's Work Together" banner had white text on a light lavender background. On my laptop it looked fine, but it was hard to read on a phone, and the button text was also light.&lt;/p&gt;

&lt;p&gt;The fix was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a deeper purple for the banner background&lt;/li&gt;
&lt;li&gt;Use white text on it&lt;/li&gt;
&lt;li&gt;Make the button text the same deep purple on a white button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can barely read something when you squint, a visitor with a small screen and bright sunlight will not read it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bonus: make every page do a different job
&lt;/h2&gt;

&lt;p&gt;My Home and About pages used the same hero, the same photo and the same story. I changed them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Home:&lt;/strong&gt; a short hero, a few skills and clear buttons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;About:&lt;/strong&gt; the full story, what I'm currently learning, and my profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When two pages say the same thing, a visitor sees no reason to open the second one. It can also confuse search engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  My rules for every change
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use only real content.&lt;/strong&gt; No invented numbers, clients or promises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One change at a time.&lt;/strong&gt; Then I look at the page on a phone-sized window and a laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove before adding.&lt;/strong&gt; Often the best fix was to delete something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check on a real phone.&lt;/strong&gt; Some problems only show up there.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;None of these changes needed a new framework or a big rewrite. They only needed me to look at my own site the way a visitor does.&lt;/p&gt;

&lt;p&gt;You can see the result here: &lt;a href="https://ritika-rajput-portfolio.vercel.app" rel="noopener noreferrer"&gt;ritika-rajput-portfolio.vercel.app&lt;/a&gt;. I also share videos on &lt;a href="https://www.youtube.com/@RitikaMoments" rel="noopener noreferrer"&gt;RitikaMoments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What is one thing on your portfolio that you know looks unfinished?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ui</category>
      <category>react</category>
      <category>beginners</category>
    </item>
    <item>
      <title>What I Learned Using Free AI Coding Agents on a Real Portfolio (OpenCode and Kilo)</title>
      <dc:creator>Ritika Rajput</dc:creator>
      <pubDate>Mon, 02 Nov 2026 06:25:33 +0000</pubDate>
      <link>https://dev.to/ritika_rajput/what-i-learned-using-free-ai-coding-agents-on-a-real-portfolio-opencode-and-kilo-210h</link>
      <guid>https://dev.to/ritika_rajput/what-i-learned-using-free-ai-coding-agents-on-a-real-portfolio-opencode-and-kilo-210h</guid>
      <description>&lt;p&gt;I'm a student, and I built and kept improving my portfolio with free AI coding agents running in the VS Code terminal. I used OpenCode and later Kilo, both on free models.&lt;/p&gt;

&lt;p&gt;They saved me a lot of time, but they also got stuck, lied by accident, and once looked busy for a whole hour without doing anything. Here is what I learned, so you can skip my mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ask for a read-only diagnosis first
&lt;/h2&gt;

&lt;p&gt;My mobile menu showed a dark overlay and an "X" button, but the menu links never appeared. My first prompt was "fix it", and the fix didn't work.&lt;/p&gt;

&lt;p&gt;What worked was splitting the job into two steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First, read only. Do not change anything.
Find where the mobile menu is rendered and tell me exactly why the panel
is invisible, with the file and line number.
Then fix it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the agent has to name the real cause, it stops guessing. In my case the real cause was a previous commit that had removed the menu panel completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep every prompt small
&lt;/h2&gt;

&lt;p&gt;My long prompts that asked for five changes at once often ended with "not done in this session". Short prompts with one job each worked much better.&lt;/p&gt;

&lt;p&gt;A prompt template I now use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change ONLY &amp;lt;one function or one section&amp;gt; in &amp;lt;one file&amp;gt;.
Do not touch anything else.
Use only text that already exists. Do not invent new content.
Do not commit or push.
Run the typecheck at the end and tell me which lines you changed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Always tell the agent not to commit or push
&lt;/h2&gt;

&lt;p&gt;Agents are happy to "help" by committing and pushing. I wanted to review every change first. So every prompt says: &lt;strong&gt;do not commit or push&lt;/strong&gt;. I check &lt;code&gt;git status&lt;/code&gt; and &lt;code&gt;git diff&lt;/code&gt; myself, and then I commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Typecheck passing does not mean the build passes
&lt;/h2&gt;

&lt;p&gt;Several times the agent said "typecheck passed", and I still had problems after deploying. The typecheck only checks types. A real build can still fail, and a failed build on Vercel means your site stays on the old version.&lt;/p&gt;

&lt;p&gt;Ask for the build too, and when it uses environment variables, give them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"5180"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;BASE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;workspace/ritika-portfolio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you ask "why is my change not live?", check the deployment status in the Vercel dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Don't let the agent test in a browser
&lt;/h2&gt;

&lt;p&gt;I once asked an agent to verify a fix with a headless browser. It spent an hour fighting &lt;code&gt;spawn pnpm ENOENT&lt;/code&gt; on Windows and trying one workaround after another. Now my prompts say: "Do not test in a browser or start a dev server. Only run typecheck and build." I look at the page myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Watch out for Windows and PowerShell
&lt;/h2&gt;

&lt;p&gt;Agents often write bash commands. On Windows PowerShell I kept seeing errors like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; is not a valid statement separator (use &lt;code&gt;;&lt;/code&gt; instead)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt; and &lt;code&gt;head&lt;/code&gt; are not recognized commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&amp;gt;/dev/null&lt;/code&gt; fails with a path error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your agent keeps hitting these, add a line like "I use Windows PowerShell, so use PowerShell syntax" to your prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. "Already implemented" needs checking
&lt;/h2&gt;

&lt;p&gt;A few times the agent answered "Already implemented, no changes needed". Sometimes that was true. Sometimes I wanted proof. I started asking it to &lt;strong&gt;name the file and line number&lt;/strong&gt; of the change, and then I searched for that text in VS Code with &lt;code&gt;Ctrl + F&lt;/code&gt;. If the agent can't point to a line, don't trust the claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Free models have limits
&lt;/h2&gt;

&lt;p&gt;Free agents can hit a daily limit, or ask you to sign in. Mine said "You need to sign in to use this model" in the middle of a task. Signing up with an email was free and took a couple of minutes. Be ready to switch tools, and never run two agents on the same folder at the same time, because they will edit the same files and cause conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Never paste secrets to an agent
&lt;/h2&gt;

&lt;p&gt;Don't paste API keys, tokens or your &lt;code&gt;.env&lt;/code&gt; file into a prompt or a chat. Keep them in environment variables and in your host's secret settings. Before pushing, search the diff for words like &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt;, &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My simple workflow now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Ask for a read-only diagnosis&lt;/li&gt;
&lt;li&gt;Ask for one small change&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git status&lt;/code&gt; and &lt;code&gt;git diff --stat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run the build locally&lt;/li&gt;
&lt;li&gt;Test the page myself, on desktop and on a phone-sized window&lt;/li&gt;
&lt;li&gt;Commit with a clear message and push&lt;/li&gt;
&lt;li&gt;Check that the deployment is &lt;strong&gt;Ready&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Check the live site in an incognito tab&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;AI agents are great helpers, but they are not a replacement for checking your own work. The more specific and small my prompts were, the better the results.&lt;/p&gt;

&lt;p&gt;You can see the portfolio I built here: &lt;a href="https://ritika-rajput-portfolio.vercel.app" rel="noopener noreferrer"&gt;ritika-rajput-portfolio.vercel.app&lt;/a&gt;. I also share videos on &lt;a href="https://www.youtube.com/@RitikaMoments" rel="noopener noreferrer"&gt;RitikaMoments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What is the biggest problem you ran into while coding with an AI agent?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Helping Google Know Who I Am: SEO for a Developer Portfolio (Person Schema, Canonical Tags, sameAs)</title>
      <dc:creator>Ritika Rajput</dc:creator>
      <pubDate>Sat, 31 Oct 2026 06:48:36 +0000</pubDate>
      <link>https://dev.to/ritika_rajput/helping-google-know-who-i-am-seo-for-a-developer-portfolio-person-schema-canonical-tags-sameas-91f</link>
      <guid>https://dev.to/ritika_rajput/helping-google-know-who-i-am-seo-for-a-developer-portfolio-person-schema-canonical-tags-sameas-91f</guid>
      <description>&lt;p&gt;My name is a common one. When I search it, results are mixed, and my own portfolio is not guaranteed to show up. So I spent some time making my portfolio easier for Google to understand.&lt;/p&gt;

&lt;p&gt;I can't promise this will rank me first, because Google decides that. But these changes give search engines clear signals about who I am. Here is what I did on a React portfolio hosted on Vercel.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Each page needs its own canonical tag
&lt;/h2&gt;

&lt;p&gt;My first problem was in Search Console. One page showed &lt;strong&gt;"Alternate page with proper canonical tag"&lt;/strong&gt; and was not indexed.&lt;/p&gt;

&lt;p&gt;URL Inspection showed the canonical was my home page (&lt;code&gt;/&lt;/code&gt;), not the page itself. A single hard-coded &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt; in &lt;code&gt;index.html&lt;/code&gt; was shared by every route, so Google treated my other pages as duplicates of the home page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; set the canonical per route.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s2"&gt;react&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;SITE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-domain.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useCanonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nf"&gt;useEffect&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;let&lt;/span&gt; &lt;span class="nx"&gt;link&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="nx"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLLinkElement&lt;/span&gt;&lt;span class="o"&gt;&amp;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;link[rel="canonical"]&lt;/span&gt;&lt;span class="dl"&gt;'&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;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;link&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canonical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&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;SITE&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&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. Unique titles and descriptions per route
&lt;/h2&gt;

&lt;p&gt;I gave every page its own title in a consistent pattern, with my name first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Home      | Your Name | Web Developer &amp;amp; UI Designer from Your City
About     | About | Your Name
Projects  | Projects | Your Name
Blog      | Blog | Your Name
Contact   | Contact | Your Name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each page also has its own meta description. For a single-page app, I update &lt;code&gt;document.title&lt;/code&gt; and the meta tags when the route changes.&lt;/p&gt;

&lt;p&gt;A tip: don't stuff keywords. Write titles and descriptions that a real person would want to click.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add Person structured data (JSON-LD)
&lt;/h2&gt;

&lt;p&gt;Structured data tells Google exactly what your site is about. For a personal site, the &lt;code&gt;Person&lt;/code&gt; type is a good fit.&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;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&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="s2"&gt;https://schema.org&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="s2"&gt;@type&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="s2"&gt;Person&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="s2"&gt;name&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="s2"&gt;Your Name&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="s2"&gt;url&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="s2"&gt;https://your-domain.com/&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="s2"&gt;image&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="s2"&gt;https://your-domain.com/your-photo.jpg&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="s2"&gt;jobTitle&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="s2"&gt;Web Developer &amp;amp; UI Designer&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="s2"&gt;address&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&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="s2"&gt;PostalAddress&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="s2"&gt;addressLocality&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="s2"&gt;Your City&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="s2"&gt;addressRegion&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="s2"&gt;Your State&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="s2"&gt;addressCountry&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="s2"&gt;Your Country&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="s2"&gt;knowsAbout&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Web development&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="s2"&gt;React&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="s2"&gt;TypeScript&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="s2"&gt;UI design&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="s2"&gt;sameAs&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.youtube.com/@your-channel&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="s2"&gt;https://www.linkedin.com/in/your-profile&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="s2"&gt;https://dev.to/your-username&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="s2"&gt;https://github.com/your-username&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="s2"&gt;https://your-blog.blogspot.com&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to be careful about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep &lt;strong&gt;only one&lt;/strong&gt; &lt;code&gt;Person&lt;/code&gt; block, and don't repeat keys like &lt;code&gt;image&lt;/code&gt; or &lt;code&gt;sameAs&lt;/code&gt;. A duplicate or a typo can break the JSON.&lt;/li&gt;
&lt;li&gt;Only add profile links that are really yours. I once nearly added a GitHub URL that was a guess, and it could have pointed to a stranger's profile. Always open every &lt;code&gt;sameAs&lt;/code&gt; link and confirm it is your account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After deploying, check it with the Schema Markup Validator at validator.schema.org.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Link your profiles to each other
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sameAs&lt;/code&gt; works best when the links go both ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put your portfolio URL in your GitHub, LinkedIn, dev.to and YouTube profiles&lt;/li&gt;
&lt;li&gt;Link to your YouTube, LinkedIn, GitHub and blog from the portfolio&lt;/li&gt;
&lt;li&gt;Use the same name everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps search engines connect all of these accounts to one person.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make the content itself clear
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One &lt;code&gt;h1&lt;/code&gt; per page that matches the page's purpose&lt;/li&gt;
&lt;li&gt;Descriptive &lt;code&gt;alt&lt;/code&gt; text on your profile photo, with your name&lt;/li&gt;
&lt;li&gt;A sitemap with every route, using your real domain&lt;/li&gt;
&lt;li&gt;Real text content on each page, not only images and cards&lt;/li&gt;
&lt;li&gt;Don't make two pages show the same content. I noticed my Home and About pages were nearly identical, and fixed it by giving each a different purpose&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Tell Google about the changes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Deploy to Vercel&lt;/li&gt;
&lt;li&gt;In Search Console, paste the URL into &lt;strong&gt;URL Inspection&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Test live URL&lt;/strong&gt;, then &lt;strong&gt;Request indexing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Submit &lt;code&gt;sitemap.xml&lt;/code&gt; under &lt;strong&gt;Sitemaps&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Indexing can take days to weeks, and requesting it again and again doesn't speed it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm watching now
&lt;/h2&gt;

&lt;p&gt;I don't have ranking results to show, and I won't pretend I do. What I'm tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the "not indexed" pages move to &lt;strong&gt;Indexed&lt;/strong&gt; in Search Console&lt;/li&gt;
&lt;li&gt;Whether my name plus "web developer" brings up my portfolio&lt;/li&gt;
&lt;li&gt;Whether my GitHub, YouTube and dev.to profiles show up together&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] A different canonical tag on every route&lt;/li&gt;
&lt;li&gt;[ ] Unique title and description per page&lt;/li&gt;
&lt;li&gt;[ ] One valid &lt;code&gt;Person&lt;/code&gt; JSON-LD block with real &lt;code&gt;sameAs&lt;/code&gt; links&lt;/li&gt;
&lt;li&gt;[ ] Profiles link back to the portfolio&lt;/li&gt;
&lt;li&gt;[ ] Sitemap submitted&lt;/li&gt;
&lt;li&gt;[ ] No duplicate pages&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;None of this is magic. It is about removing confusion for search engines and keeping your online presence consistent.&lt;/p&gt;

&lt;p&gt;You can see the portfolio here: &lt;a href="https://ritika-rajput-portfolio.vercel.app" rel="noopener noreferrer"&gt;ritika-rajput-portfolio.vercel.app&lt;/a&gt;. I also share videos on &lt;a href="https://www.youtube.com/@RitikaMoments" rel="noopener noreferrer"&gt;RitikaMoments&lt;/a&gt; and write on &lt;a href="https://dev.to/ritika_rajput"&gt;dev.to&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have you tried improving your own search presence? What worked for you?&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>react</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How I Use Supabase as a Simple CMS for My Portfolio Blog</title>
      <dc:creator>Ritika Rajput</dc:creator>
      <pubDate>Fri, 30 Oct 2026 08:02:49 +0000</pubDate>
      <link>https://dev.to/ritika_rajput/how-i-use-supabase-as-a-simple-cms-for-my-portfolio-blog-3gkp</link>
      <guid>https://dev.to/ritika_rajput/how-i-use-supabase-as-a-simple-cms-for-my-portfolio-blog-3gkp</guid>
      <description>&lt;p&gt;When I added a blog to my portfolio, I did not want to edit code and redeploy the site every time I published a post. A small Supabase database solved this for me. In this post I'll show a simple setup you can copy: a table for posts, a storage bucket for images, and a few lines of code to read them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a posts table
&lt;/h2&gt;

&lt;p&gt;In the Supabase SQL editor, create a table with the fields a blog card needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;excerpt&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cover_image&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;slug&lt;/code&gt; is used for the post URL, for example &lt;code&gt;/blog/my-first-post&lt;/code&gt;. Making it unique makes sure two posts never share one address.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Add a storage bucket for cover images
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Storage&lt;/strong&gt;, create a bucket (I named mine &lt;code&gt;site-images&lt;/code&gt;) and make it public. Upload an image and copy its public URL. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://YOUR-PROJECT.supabase.co/storage/v1/object/public/site-images/my-cover.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste that link into the &lt;code&gt;cover_image&lt;/code&gt; column of your post. A tip: compress cover images (WebP at around 1200 x 630 works well) so your blog cards load quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Read the posts in your frontend
&lt;/h2&gt;

&lt;p&gt;Install the client and create it with your project URL and anon key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @supabase/supabase-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&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;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://YOUR-PROJECT.supabase.co&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;YOUR-ANON-KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To show the list of blog cards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&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;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title, excerpt, slug, date, cover_image&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="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&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;ascending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To load one post on its own page, filter by slug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&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;error&lt;/span&gt; &lt;span class="p"&gt;}&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;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&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="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you write &lt;code&gt;content&lt;/code&gt; in Markdown, render it with a library such as &lt;code&gt;react-markdown&lt;/code&gt; or &lt;code&gt;marked&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keep it secure with Row Level Security
&lt;/h2&gt;

&lt;p&gt;By default you do not want visitors to change your data. Turn on Row Level Security and allow only reading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"Public can read posts"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With no insert, update or delete policy, visitors can only read. The anon key is meant to be used in the browser, but never put your &lt;code&gt;service_role&lt;/code&gt; key in frontend code.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Publishing a new post
&lt;/h2&gt;

&lt;p&gt;To publish, I open the &lt;strong&gt;Table Editor&lt;/strong&gt;, insert a new row, paste the title, excerpt, slug and content, and add the cover image link. The post shows up on the site without any code change or redeploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Simple and free to start, and no redeploy for new posts.&lt;/li&gt;
&lt;li&gt;You control your data and can export it any time.&lt;/li&gt;
&lt;li&gt;The Table Editor is not a writing tool, so long posts feel a bit clumsy. If you publish often, you can later build a small admin page on top of the same table.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;For a personal portfolio, a table, a bucket and a few queries are enough to get a working blog. You can see this kind of setup on the blog section of &lt;a href="https://ritika-rajput-portfolio.vercel.app/blog" rel="noopener noreferrer"&gt;my portfolio&lt;/a&gt;, and you can find more about my work on the &lt;a href="https://ritika-rajput-portfolio.vercel.app/skills" rel="noopener noreferrer"&gt;Skills page&lt;/a&gt;. If you try this, let me know in the comments how it went.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>supabase</category>
      <category>beginners</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Automated My Daily Blog with GitHub Actions, Gemini and the Blogger API</title>
      <dc:creator>Ritika Rajput</dc:creator>
      <pubDate>Wed, 28 Oct 2026 07:21:15 +0000</pubDate>
      <link>https://dev.to/ritika_rajput/i-automated-my-daily-blog-with-github-actions-gemini-and-the-blogger-api-403h</link>
      <guid>https://dev.to/ritika_rajput/i-automated-my-daily-blog-with-github-actions-gemini-and-the-blogger-api-403h</guid>
      <description>&lt;p&gt;I wanted my blog to publish a new post every morning without me touching it. So I built a small pipeline: GitHub Actions runs a Python script on a schedule, Gemini writes the post, and the Blogger API publishes it.&lt;/p&gt;

&lt;p&gt;It worked, but only after I hit three errors that the tutorials never mention. Here is the setup and the fixes.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; triggers the workflow every day on a cron schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;generate_post.py&lt;/code&gt;&lt;/strong&gt; asks the Gemini API to write one post about a topic from a &lt;code&gt;skills.txt&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;post_to_blogger.py&lt;/code&gt;&lt;/strong&gt; publishes it through the Blogger API.&lt;/li&gt;
&lt;li&gt;The workflow commits the saved post file so topics don't repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All credentials live in &lt;strong&gt;GitHub Secrets&lt;/strong&gt;, never in the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BLOGGER_CLIENT_ID
BLOGGER_CLIENT_SECRET
BLOGGER_REFRESH_TOKEN
BLOGGER_BLOG_ID
GEMINI_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Daily Blogger Auto-Post&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# 10:00 AM IST (cron runs in UTC)&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate post&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/generate_post.py&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GEMINI_API_KEY }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Post to Blogger&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/post_to_blogger.py&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;BLOGGER_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLOGGER_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;BLOGGER_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLOGGER_CLIENT_SECRET }}&lt;/span&gt;
          &lt;span class="na"&gt;BLOGGER_REFRESH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLOGGER_REFRESH_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;BLOGGER_BLOG_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLOGGER_BLOG_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that GitHub cron uses &lt;strong&gt;UTC&lt;/strong&gt;. For 10:00 AM in India (UTC+5:30), the cron is &lt;code&gt;30 4 * * *&lt;/code&gt;. Scheduled runs can also start 10 to 30 minutes late, so I schedule a little earlier than I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error 1: &lt;code&gt;GEMINI_API_KEY is not set&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;My first run failed in 13 seconds with exactly that message. The key was in my local &lt;code&gt;.env&lt;/code&gt;, but GitHub never sees your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; add it under &lt;strong&gt;Settings → Secrets and variables → Actions → New repository secret&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Secret names can only contain letters, numbers and underscores. If GitHub rejects the name, you probably pasted the whole &lt;code&gt;NAME=value&lt;/code&gt; line into the name field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error 2: the model was retired (HTTP 404)
&lt;/h2&gt;

&lt;p&gt;The next run failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This model models/gemini-2.0-flash is no longer available.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Model names get retired. Hard-coding one means your automation silently dies one day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; read the model from an environment variable, and if the API returns 404, ask the ListModels endpoint for a current "flash" model that supports &lt;code&gt;generateContent&lt;/code&gt;, then retry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pick_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://generativelanguage.googleapis.com/v1beta/models&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models&lt;/span&gt;&lt;span class="sh"&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;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generateContent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;supportedGenerationMethods&lt;/span&gt;&lt;span class="sh"&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No usable Gemini flash model found&lt;/span&gt;&lt;span class="sh"&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;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;pick_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also retry 3 times and validate the output so a half-written post never gets published.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error 3: the refresh token that expires
&lt;/h2&gt;

&lt;p&gt;If your Google OAuth app is in &lt;strong&gt;Testing&lt;/strong&gt; mode, refresh tokens expire after about 7 days. Your automation works all week, then dies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; in Google Cloud Console, open the OAuth consent screen and move the app to &lt;strong&gt;In production&lt;/strong&gt;. Then generate a new refresh token and update it in both &lt;code&gt;.env&lt;/code&gt; and the GitHub Secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it safe to run daily
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency:&lt;/strong&gt; save each post as &lt;code&gt;posts/&amp;lt;date&amp;gt;-&amp;lt;slug&amp;gt;.md&lt;/code&gt; and skip if today's post already exists, so a manual re-run does not create duplicates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality guardrails:&lt;/strong&gt; reject drafts that invent personal claims like fake clients or made-up percentages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail soft on images:&lt;/strong&gt; if the image fetch fails, publish without one instead of failing the whole run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test as a draft first:&lt;/strong&gt; run once manually with drafts enabled, check Blogger, then switch to publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never print secrets&lt;/strong&gt; in logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A note on quality
&lt;/h2&gt;

&lt;p&gt;Automation should not mean spam. I keep topics tied to real skills, ask for practical guides instead of invented stories, and read the output before trusting it. Search engines care about useful content, not volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The pipeline is small, but the lessons are general: secrets live outside your repo, model names expire, and OAuth apps in testing mode expire too.&lt;/p&gt;

&lt;p&gt;If you want to see where my posts end up, check my portfolio at &lt;a href="https://ritika-rajput-portfolio.vercel.app" rel="noopener noreferrer"&gt;ritika-rajput-portfolio.vercel.app&lt;/a&gt;, and my videos on &lt;a href="https://www.youtube.com/@RitikaMoments" rel="noopener noreferrer"&gt;RitikaMoments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have you automated something similar? What broke first for you?&lt;/p&gt;

</description>
      <category>python</category>
      <category>githubactions</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>3 Bugs I Fixed in My React Portfolio (Mobile Menu, Canonical Tags, Image Sizing)</title>
      <dc:creator>Ritika Rajput</dc:creator>
      <pubDate>Wed, 28 Oct 2026 07:15:27 +0000</pubDate>
      <link>https://dev.to/ritika_rajput/3-bugs-i-fixed-in-my-react-portfolio-mobile-menu-canonical-tags-image-sizing-2773</link>
      <guid>https://dev.to/ritika_rajput/3-bugs-i-fixed-in-my-react-portfolio-mobile-menu-canonical-tags-image-sizing-2773</guid>
      <description>&lt;p&gt;I recently built my portfolio with React, TypeScript, Tailwind and Vercel. After launching it, I hit three bugs that were easy to miss but hurt real visitors. Here is what went wrong and how I fixed each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The mobile menu that never opened
&lt;/h2&gt;

&lt;p&gt;On phones, tapping the hamburger icon dimmed the screen and turned the icon into an "X", but the menu links never appeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dimmed backdrop plus an "X" means your state toggle works. The problem is the panel itself.&lt;/li&gt;
&lt;li&gt;Check your CSS variables first. If something like &lt;code&gt;--mobile-menu-bg&lt;/code&gt; is undefined, the panel can be transparent.&lt;/li&gt;
&lt;li&gt;Animations that start at &lt;code&gt;opacity: 0&lt;/code&gt; can get stuck invisible.&lt;/li&gt;
&lt;li&gt;A parent with &lt;code&gt;overflow: hidden&lt;/code&gt; or &lt;code&gt;transform&lt;/code&gt; can clip an &lt;code&gt;absolute&lt;/code&gt; child.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; render the panel as &lt;code&gt;position: fixed&lt;/code&gt;, give it a solid background with a fallback value, and keep it above the backdrop.&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="nt"&gt;div&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mobile-menu"&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-x-3 top-20 z-[110] rounded-2xl bg-[var(--mobile-menu-bg,#0b1530)] p-2"&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;links&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;l&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&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;"flex min-h-[48px] items-center px-4 text-white"&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;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&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;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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="nt"&gt;div&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;Also keep touch targets at least 44px, close the menu on link click, outside click and the Escape key, and add &lt;code&gt;aria-label&lt;/code&gt; and &lt;code&gt;aria-expanded&lt;/code&gt; to the button.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Google refused to index my /portfolio page
&lt;/h2&gt;

&lt;p&gt;Search Console said: &lt;strong&gt;"Alternate page with proper canonical tag"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;URL Inspection showed the user-declared canonical was my home page (&lt;code&gt;/&lt;/code&gt;) instead of &lt;code&gt;/portfolio&lt;/code&gt;. A single hard-coded canonical in &lt;code&gt;index.html&lt;/code&gt; was shared by every route, so Google treated each page as a duplicate of the home page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; set the canonical per route.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s2"&gt;react&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;SITE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-domain.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useCanonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nf"&gt;useEffect&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;let&lt;/span&gt; &lt;span class="nx"&gt;link&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="nx"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLLinkElement&lt;/span&gt;&lt;span class="o"&gt;&amp;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;link[rel="canonical"]&lt;/span&gt;&lt;span class="dl"&gt;'&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;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;link&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canonical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&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;SITE&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&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;Give every route its own title and meta description too, and list all routes in &lt;code&gt;sitemap.xml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Images that looked wrong on different screens
&lt;/h2&gt;

&lt;p&gt;My profile photo was too big on small phones and too small on large screens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; size it with &lt;code&gt;clamp()&lt;/code&gt; and keep the aspect ratio fixed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.profile-photo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60vw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;440px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;object-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;top&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;Add &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes to avoid layout jumps, set &lt;code&gt;max-width: 100%&lt;/code&gt; on images, and always include &lt;code&gt;&amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Test the mobile menu on a real phone, not only desktop resize&lt;/li&gt;
&lt;li&gt;[ ] Check the canonical tag on every route&lt;/li&gt;
&lt;li&gt;[ ] Verify pages in Google Search Console's URL Inspection&lt;/li&gt;
&lt;li&gt;[ ] Test at 360px, 390px, 768px, 1280px and 1920px widths&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;None of these bugs showed up in a quick desktop check, but they affected real users and search visibility. Test on phones, and inspect what Google actually sees.&lt;/p&gt;

&lt;p&gt;You can see the portfolio here: &lt;a href="https://ritika-rajput-portfolio.vercel.app" rel="noopener noreferrer"&gt;ritika-rajput-portfolio.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also share videos on my YouTube channel, &lt;a href="https://www.youtube.com/@RitikaMoments" rel="noopener noreferrer"&gt;RitikaMoments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What was the last "invisible" bug you found in your own project? Tell me in the comments.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>seo</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>How I built and launched my portfolio website as a beginner, using AI tools</title>
      <dc:creator>Ritika Rajput</dc:creator>
      <pubDate>Fri, 09 Oct 2026 06:47:52 +0000</pubDate>
      <link>https://dev.to/ritika_rajput/how-i-built-and-launched-my-portfolio-website-as-a-beginner-using-ai-tools-4gf2</link>
      <guid>https://dev.to/ritika_rajput/how-i-built-and-launched-my-portfolio-website-as-a-beginner-using-ai-tools-4gf2</guid>
      <description>&lt;p&gt;Hi, I'm Ritika Rajput, a web developer, video editor and social media manager from India. I wanted a portfolio website, but I don't write HTML, CSS or React from scratch. So I built it with AI tools. Here is what that really looked like, including what went wrong.&lt;/p&gt;

&lt;p&gt;Live site: &lt;a href="https://ritika-rajput-portfolio.vercel.app" rel="noopener noreferrer"&gt;https://ritika-rajput-portfolio.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools I used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replit Agent to generate the first version&lt;/li&gt;
&lt;li&gt;VS Code with OpenCode (an AI coding agent) for later changes&lt;/li&gt;
&lt;li&gt;GitHub to save and version my code&lt;/li&gt;
&lt;li&gt;Vercel to host the site&lt;/li&gt;
&lt;li&gt;Supabase for the database behind my admin panel&lt;/li&gt;
&lt;li&gt;Google Search Console to get the site indexed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What went wrong, and what I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. AI writes placeholder text that looks real.&lt;/strong&gt; My first version had generic lines about "an independent creative studio" and even an invented year ("Since 1998") on a client project. I had to read every line and replace it with true information. Always review what the AI writes about you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. My first deployment failed.&lt;/strong&gt; Vercel stopped with the error "PORT environment variable is required". The fix was adding the missing environment variables in the Vercel settings and redeploying. [Add one line about how you felt or how long it took.]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Database security is not optional.&lt;/strong&gt; My Supabase tables use Row Level Security. For now they allow public reading only, and I plan to add a proper login before allowing any writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Be honest about your skills.&lt;/strong&gt; I removed the fake percentage bars from my Skills page and listed the tools I actually use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. SEO basics are small but important.&lt;/strong&gt; I added page titles, meta descriptions, alt text for images, a sitemap.xml and a robots.txt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;Pages: Home, About, Skills, Blog, Projects and Contact, plus a hidden admin panel for managing content.&lt;/p&gt;

&lt;p&gt;My first client project is a website for Nirmal Family Restaurant &amp;amp; Party Hall: &lt;a href="https://nirmal-restaurant.vercel.app/" rel="noopener noreferrer"&gt;https://nirmal-restaurant.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;I want to make my contact form send real messages, publish real blog posts, and add more projects (video editing, graphic design and social media).&lt;/p&gt;

&lt;p&gt;If you are a beginner and think an AI-built website is not "real", I disagree. It is real when you review it, test it and understand what you ship. Feedback is very welcome!&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgcltianke4tk5gn8omuq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgcltianke4tk5gn8omuq.png" alt=" " width="800" height="321"&gt;&lt;/a&gt;&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1a6mb0540mdysfyqmkaf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1a6mb0540mdysfyqmkaf.PNG" alt=" " width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>ai</category>
      <category>portfolio</category>
    </item>
  </channel>
</rss>
