<?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: Alan</title>
    <description>The latest articles on DEV Community by Alan (@alanlucena).</description>
    <link>https://dev.to/alanlucena</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3882823%2Fbe4f8046-1f35-46ee-a69c-94ba71acfdff.png</url>
      <title>DEV Community: Alan</title>
      <link>https://dev.to/alanlucena</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alanlucena"/>
    <language>en</language>
    <item>
      <title>From zero to 22K daily Google impressions: lessons building Hablaaa.com with Next.js 16 + Supabase</title>
      <dc:creator>Alan</dc:creator>
      <pubDate>Thu, 16 Apr 2026 16:30:10 +0000</pubDate>
      <link>https://dev.to/alanlucena/from-zero-to-22k-daily-google-impressions-lessons-building-hablaaacom-with-nextjs-16-supabase-pb1</link>
      <guid>https://dev.to/alanlucena/from-zero-to-22k-daily-google-impressions-lessons-building-hablaaacom-with-nextjs-16-supabase-pb1</guid>
      <description>&lt;p&gt;ix months ago I started building Hablaaa, a collaborative Spanish slang dictionary covering 22 Spanish-speaking countries. Think Urban Dictionary, but for expressions like "chido" (Mexico), "bacán" (Ecuador), "pana" (Venezuela), and 5,000 more regional words.&lt;/p&gt;

&lt;p&gt;It's bootstrapped, solo-built, bilingual, and currently pulling 20K+ daily Google impressions (peaking at 22K), 210K impressions in the last 28 days, and growing 41% week-over-week. All organic.&lt;/p&gt;

&lt;p&gt;Here are the technical decisions that actually moved the needle.&lt;/p&gt;

&lt;p&gt;The stack&lt;br&gt;
&lt;strong&gt;Next.js 16 with Turbopack&lt;/strong&gt; for the frontend&lt;br&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; for Postgres + Row Level Security + Auth&lt;br&gt;
&lt;strong&gt;TypeScript 5.7, Zod **for runtime validation&lt;br&gt;
**Tailwind 3.4 + Shadcn UI&lt;/strong&gt; for components&lt;br&gt;
&lt;strong&gt;Satori&lt;/strong&gt; via next/og for dynamic OG images&lt;br&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; for hosting and ISR&lt;br&gt;
Nothing exotic. The interesting stuff is how these pieces combine.&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenge 1: Bilingual routing without duplicate content
&lt;/h2&gt;

&lt;p&gt;Every word page lives at two URLs:&lt;/p&gt;

&lt;p&gt;/palabra/&lt;a href="https://dev.toSpanish,%20primary"&gt;slug&lt;/a&gt;&lt;br&gt;
/en/word/&lt;a href="https://dev.toEnglish,%20translation"&gt;slug&lt;/a&gt;&lt;br&gt;
To avoid duplicate content penalties, each page declares hreflang alternates in metadata:&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&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="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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;getWordData&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`¿Qué significa &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;palabra&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;? Definición y ejemplos`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;canonical&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;SITE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/palabra/&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;es&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;SITE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/palabra/&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;english_definition&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;SITE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/en/word/&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="s2"&gt;`&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-default&lt;/span&gt;&lt;span class="dl"&gt;'&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;SITE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/palabra/&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="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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The x-default pointing to Spanish tells Google: "if you don't know the user's language, default to this". Important because Google's AI overview started citing the correct language version after adding this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge 2: Phonetic search for informal Spanish
&lt;/h2&gt;

&lt;p&gt;Spanish slang has no spelling standard. Users type "awebo" looking for "a huevo", or "wey" looking for "güey". I needed fuzzy search that understands Spanish phonetics without importing a heavyweight ML model.&lt;/p&gt;

&lt;p&gt;Solution: a Postgres function that normalizes phonetics directly in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge 3: Dynamic OG images with country flags
&lt;/h2&gt;

&lt;p&gt;Each word page needs a unique OG image with the word, country flags, and a decorative background. Satori can't use next/image and doesn't support variable fonts.&lt;/p&gt;

&lt;p&gt;Key gotcha: Satori requires display: 'flex' on any div with more than one child. The image route uses edge runtime for ~200ms generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;OgImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&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="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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;word&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;getWord&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flagDataUri&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;loadFlagAsDataUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paises&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ImageResponse&lt;/span&gt;&lt;span class="p"&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;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1200px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;630px&lt;/span&gt;&lt;span class="dl"&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;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;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getResponsiveSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;palabra&lt;/span&gt;&lt;span class="p"&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;gt;&lt;/span&gt;
          "&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;palabra&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;"
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;flagDataUri&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"80"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"80"&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;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Montserrat&lt;/span&gt;&lt;span class="dl"&gt;'&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadFont&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flags load as data URIs from local SVG files. No CDN round-trip, no layout shift, no preview glitches when shared on social.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO wins worth stealing
&lt;/h2&gt;

&lt;p&gt;Three things that materially moved rankings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Killed keyword cannibalization.&lt;/strong&gt; The /diccionario index page showed every word with its full definition inline. Google ranked that page for specific word queries instead of individual word pages. Removed inline definitions from the index. Impressions on word pages jumped 41% week-over-week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Added IndexNow for Bing and Yandex.&lt;/strong&gt; The daily indexing script now pushes all URLs via the IndexNow protocol. Indexation dropped from weeks to hours on Bing. Also unlocked Microsoft Copilot citations, currently sitting at 580 citations in three months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Switched trending logic to real page views.&lt;/strong&gt; Started as random ordering with a votos_count fallback. Added a ViewLogger client component that fires on mount, deduplicated by IP hash. Now the trending page shows what people actually read, which dramatically improves internal linking relevance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the numbers are today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;9,450 URLs submitted&lt;/strong&gt; across entries and articles (bilingual)&lt;br&gt;
&lt;strong&gt;7,730 indexed by Google,&lt;/strong&gt; 82% coverage, remaining 2,130 processing this month&lt;br&gt;
&lt;strong&gt;210K impressions in 28 days&lt;/strong&gt;, averaging 22K daily with position 11.6&lt;br&gt;
&lt;strong&gt;Week-over-week growth: +41% impressions, +68% clicks&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;580 Microsoft Copilot citations&lt;/strong&gt; in three months, trending up steeply since April&lt;br&gt;
&lt;strong&gt;Full stack solo&lt;/strong&gt;, one repo, one developer, ~2,000 commits&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Backlink outreach (the real multiplier, just landed AlternativeTo, Crunchbase, and LinkedIn this week), Mundial 2026 content prep (June tourism spike expected with 40+ articles already queued), and AdSense application in May once position stabilizes.&lt;/p&gt;

&lt;p&gt;If you're curious, hablaaa.com is live. Happy to chat stack, SEO, or multilingual SaaS architecture.&lt;/p&gt;

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