1,104 Páginas Estáticas Para Conectar Fontaneros: Por Qué Construí un Directorio Local en 2025
La idea que nadie quiere tocar
Mira, voy a ser directo: cuando le dije a otros desarrolladores que iba a construir un directorio de fontaneros de emergencia en 2025, la mayoría me miró como si estuviera loco.
"Los directorios locales están muertos desde 2015", me dijeron. "Google My Business lo domina todo", insistieron.
Y tenían razón... hasta cierto punto.
Pero hay algo que nadie menciona: cuando se te rompe una tubería a las 3 AM y tu casa se está inundando, no vas a ponerte a leer reseñas de Google durante 20 minutos. Necesitas un número de teléfono de alguien disponible. Ya.
Ese es el problema real que quise resolver con [Find Emergency Plumber](https://github.com/brianMena/find-emergency-plumber).
Los números reales del proyecto
Antes de entrar en el código, déjame mostrarte lo que construí:
→ 1,104 páginas estáticas generadas en build time → 90 ciudades en Estados Unidos cubiertas → 1,251 fontaneros verificados con puntuaciones de calidad → Next.js 16 con App Router para máxima velocidad → Supabase como base de datos PostgreSQL → Sanity CMS para gestionar contenido del blog
Todo esto desplegado en Vercel, con un tema oscuro estilo Telegram que me encanta.
¿Por qué construir para el mercado estadounidense desde España? Porque el mercado es más grande y el problema es universal. Una tubería rota en Miami es tan urgente como una en Madrid.
La decisión técnica que lo cambió todo
Lo interesante de este proyecto no es *qué* construí, sino cómo lo construí.
La mayoría de los directorios locales usan renderizado dinámico: haces una búsqueda, el servidor consulta la base de datos, renderiza la página, te la envía. Es lento y malo para SEO.
Yo fui en la dirección opuesta: generación estática masiva.
Por qué 1,104 páginas estáticas
Cada combinación de ciudad + servicio se genera como una página HTML estática en tiempo de build:
```typescript // Ejemplo simplificado de la estructura export async function generateStaticParams() { const cities = await getCities(); // 90 ciudades const services = ['emergency', '24-7', 'repair', 'installation'];
return cities.flatMap(city => services.map(service => ({ city: city.slug, service: service })) ); } ```
Cada página se genera una vez durante el build y se sirve instantáneamente. Google adora esto porque:
1. Velocidad brutal: No hay tiempo de consulta a BD 2. HTML puro: Los crawlers pueden indexar todo de inmediato 3. Cache-friendly: Vercel puede servir todo desde edge
El stack que elegí (y por qué)
Next.js 16 con App Router: App Router de Next.js 16 hace que la generación estática sea trivial. Usé TypeScript en modo estricto porque los errores de tipos en producción son caros.
Supabase (PostgreSQL): Necesitaba una base de datos relacional para los fontaneros, ciudades, y sus relaciones. Supabase me da PostgreSQL con un SDK excelente y edge functions si las necesito más adelante.
Sanity CMS: Para el blog necesitaba algo más flexible que markdown simple. Sanity v3 me permite editar contenido con preview en tiempo real y tiene una API rápida.
Tailwind CSS 4: El nuevo Tailwind con CSS nativo es significativamente más rápido en desarrollo. No hay más conflictos con utilidades `py-*` (sí, me pasó y lo tuve que arreglar en un commit reciente).
```typescript // package.json principales dependencias { "next": "16.x", "react": "19.2", "@supabase/supabase-js": "latest", "next-sanity": "latest", "tailwindcss": "4.x" } ```
Los problemas reales que nadie te cuenta
Construir esto no fue un camino recto. Te cuento los problemas reales que enfrenté:
1. Google Search Console me odiaba
Durante semanas, Google no indexaba mis páginas. El problema: había conflictos entre `www` y non-www en los canonical tags. Tuve que:
- Forzar redirecciones 301 consistentes
- Arreglar canonical URLs en el sitemap
- Bloquear assets estáticos del crawling para no desperdiciar crawl budget
Commit real del proyecto: ``` fix: GSC indexation issues - www canonical, redirects, and static asset blocking ```
Esta es la realidad: el SEO técnico importa tanto como el contenido.
2. Vercel Image Optimization vs Sanity
Vercel cobra por la optimización de imágenes más allá del tier gratuito. Las imágenes de Sanity pasaban por el optimizador de Vercel y me estaba quemando el presupuesto.
Solución: bypass del optimizador de Vercel para imágenes de Sanity, usando directamente la CDN de Sanity que ya optimiza las imágenes.
```typescript // Usar el loader de Sanity directamente import imageUrlBuilder from '@sanity/image-url';
const builder = imageUrlBuilder(sanityClient);
function urlFor(source: any) { return builder.image(source); } ```
3. Null-checking en todas partes
Cuando integras un CMS externo como Sanity, asumes que los datos están bien formateados. Spoiler: no siempre lo están.
Tuve que agregar null-checking defensivo en todos los componentes que renderizan contenido de Sanity:
```typescript // Antes (crasheaba en producción) const imageUrl = post.mainImage.asset.url;
// Después (defensivo) const imageUrl = post.mainImage?.asset?.url ?? '/fallback.jpg'; ```
Lo que viene: SMS y llamadas en tiempo real
El proyecto funciona, pero falta la pieza clave: comunicación instantánea.
Cuando alguien necesita un fontanero de emergencia a las 3 AM, no quiere llenar un formulario y esperar un email. Quiere:
1. Hacer clic en "Llamar ahora" 2. Que el sistema llame al fontanero disponible más cercano 3. Conectar la llamada en segundos
Esto requiere integración con Twilio u otro proveedor de telefonía. La arquitectura sería:
→ Usuario hace clic en "Emergencia" → Webhook a Twilio API → Twilio llama a fontaneros por orden de proximidad → Primer fontanero que responde se conecta con el usuario
Es la siguiente fase del proyecto. Lo interesante es que el SEO estático ya está funcionando para atraer tráfico; ahora toca optimizar la conversión.
Takeaways para tu próximo proyecto
1. Generación estática > Dinámico para directorios Si tu contenido no cambia cada segundo, genera páginas estáticas. El SEO y la velocidad valen la pena.
2. Los problemas pequeños te matan en producción Canonical URLs, null-checking, presupuestos de imagen... estos "detalles" pueden hundir un proyecto.
3. Construir para mercados grandes desde donde estés Estoy en España, construyo para USA. El internet no tiene fronteras, úsalo a tu favor.
4. Resuelve un problema urgente, no uno conveniente Una tubería rota es un problema urgente. La gente paga por urgencia, no por conveniencia.
5. Ship rápido, itera después Este proyecto tiene 5 meses de commits. No esperé a tener Twilio integrado para lanzar. Lancé con lo esencial y estoy iterando.
El directorio local no está muerto
Lo que está muerto son los directorios locales mal hechos: lentos, mal optimizados, sin resolver problemas reales.
Pero si construyes algo rápido, bien optimizado, y que resuelve un problema urgente real, hay espacio.
1,104 páginas estáticas. 1,251 fontaneros. 90 ciudades.
Esto es construir en público: te muestro los números reales, los commits que arreglaron bugs, las decisiones técnicas que tomé.
¿Qué vas a construir tú?
---
*Puedes ver el código completo del proyecto en [GitHub](https://github.com/brianMena/find-emergency-plumber). Todo el stack, todas las decisiones, todo documentado.*
