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.
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.
Here are the technical decisions that actually moved the needle.
The stack
Next.js 16 with Turbopack for the frontend
Supabase for Postgres + Row Level Security + Auth
TypeScript 5.7, Zod **for runtime validation
**Tailwind 3.4 + Shadcn UI for components
Satori via next/og for dynamic OG images
Vercel for hosting and ISR
Nothing exotic. The interesting stuff is how these pieces combine.
Challenge 1: Bilingual routing without duplicate content
Every word page lives at two URLs:
/palabra/slug
/en/word/slug
To avoid duplicate content penalties, each page declares hreflang alternates in metadata:
export async function generateMetadata({ params }) {
const { slug } = await params
const data = await getWordData(slug)
return {
title: `¿Qué significa ${data.palabra}? Definición y ejemplos`,
alternates: {
canonical: `${SITE_URL}/palabra/${slug}`,
languages: {
es: `${SITE_URL}/palabra/${slug}`,
en: data.english_definition
? `${SITE_URL}/en/word/${slug}`
: undefined,
'x-default': `${SITE_URL}/palabra/${slug}`,
},
},
}
}
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.
Challenge 2: Phonetic search for informal Spanish
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.
Solution: a Postgres function that normalizes phonetics directly in the database.
Challenge 3: Dynamic OG images with country flags
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.
Key gotcha: Satori requires display: 'flex' on any div with more than one child. The image route uses edge runtime for ~200ms generation:
export const runtime = 'edge'
export default async function OgImage({ params }) {
const { slug } = await params
const word = await getWord(slug)
const flagDataUri = await loadFlagAsDataUri(word.paises[0])
return new ImageResponse(
(
<div style={{ display: 'flex', width: '1200px', height: '630px' }}>
<span style={{ fontSize: getResponsiveSize(word.palabra) }}>
"{word.palabra}"
</span>
<img src={flagDataUri} width="80" height="80" />
</div>
),
{
fonts: [{ name: 'Montserrat', data: await loadFont() }],
width: 1200,
height: 630,
}
)
}
Flags load as data URIs from local SVG files. No CDN round-trip, no layout shift, no preview glitches when shared on social.
SEO wins worth stealing
Three things that materially moved rankings:
1. Killed keyword cannibalization. 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.
2. Added IndexNow for Bing and Yandex. 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.
3. Switched trending logic to real page views. 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.
Where the numbers are today
9,450 URLs submitted across entries and articles (bilingual)
7,730 indexed by Google, 82% coverage, remaining 2,130 processing this month
210K impressions in 28 days, averaging 22K daily with position 11.6
Week-over-week growth: +41% impressions, +68% clicks
580 Microsoft Copilot citations in three months, trending up steeply since April
Full stack solo, one repo, one developer, ~2,000 commits
What's next
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.
If you're curious, hablaaa.com is live. Happy to chat stack, SEO, or multilingual SaaS architecture.
Top comments (0)