The Problem: Managing Thousands of Listings Without Losing Your Mind
When I started Gestorías Cerca de Mí, I had a simple but brutal idea: create the most comprehensive directory of administrative service providers in Spain. The problem wasn't the idea. The problem was: how do I manage 900+ businesses with information that changes constantly?
I could have built a custom admin panel in Next.js. Many developers do. But after working with raw databases, I know that's a trap. You end up maintaining interfaces instead of focusing on what really matters: ensuring data is correct and accessible.
So I decided to use Sanity CMS as the content layer. It wasn't an obvious choice, but it turned out to be the right one.
Why Sanity CMS Instead of Alternatives
Before choosing Sanity, I considered several options:
- **WordPress with plugins**: Too coupled. If I wanted to change the frontend, I was stuck.
- **Contentful**: Excellent, but felt like overkill for this specific use case.
- **Custom Next.js panel**: Total control, but infinite maintenance.
- **Sanity**: Headless, flexible, and with a visual editor that non-technical people can use.
I chose Sanity because:
1. Clear separation between content and presentation
With Sanity, content lives in its own universe. The frontend (Next.js) is just a consumer. If tomorrow I want to create a mobile app, a public API, or integrate with another platform, I don't have to touch the CMS. Data simply flows.
2. Flexible structure for complex data
A gestoría isn't just a name and phone number. It has:
- Services offered (multiple, with descriptions)
- Location (postal code, province, coordinates)
- Hours (can vary by day)
- Contact information (phone, email, website)
- Reviews and ratings
- Specialties
In Sanity, I defined complex schemas that let editors add information without breaking anything. Arrays, nested objects, document references... everything works naturally.
3. The visual editor is incredible
It's not just me using the CMS. Eventually I'll need other people adding and editing gestorías. Sanity's editor is intuitive. Non-technical people can navigate, edit fields, and see changes in real-time without extensive training.
The Architecture: How I Structured the Data
This is the base structure I created in Sanity:
```javascript // schemas/gestoria.js export default { name: 'gestoria', title: 'Gestoría', type: 'document', fields: [ { name: 'nombre', title: 'Nombre de la Gestoría', type: 'string', validation: Rule => Rule.required() }, { name: 'slug', title: 'URL Slug', type: 'slug', options: { source: 'nombre', maxLength: 96, }, validation: Rule => Rule.required() }, { name: 'ubicacion', title: 'Ubicación', type: 'object', fields: [ { name: 'codigoPostal', type: 'string', title: 'Código Postal' }, { name: 'provincia', type: 'string', title: 'Provincia' }, { name: 'ciudad', type: 'string', title: 'Ciudad' }, { name: 'direccion', type: 'text', title: 'Dirección Completa' }, { name: 'latitud', type: 'number', title: 'Latitud' }, { name: 'longitud', type: 'number', title: 'Longitud' } ] }, { name: 'servicios', title: 'Servicios Ofrecidos', type: 'array', of: [ { type: 'object', fields: [ { name: 'nombre', type: 'string', title: 'Nombre del Servicio' }, { name: 'descripcion', type: 'text', title: 'Descripción' } ] } ] }, { name: 'contacto', title: 'Información de Contacto', type: 'object', fields: [ { name: 'telefono', type: 'string', title: 'Teléfono' }, { name: 'email', type: 'string', title: 'Email' }, { name: 'sitioWeb', type: 'url', title: 'Sitio Web' } ] }, { name: 'rating', title: 'Calificación', type: 'number', validation: Rule => Rule.min(0).max(5) }, { name: 'numeroResenas', title: 'Número de Reseñas', type: 'number' } ] } ```
This structure allows:
- Searching by postal code (crucial for UX)
- Filtering by services
- Sorting by rating
- Keeping contact information updated
The Integration: Next.js + Supabase + Sanity
Here's the interesting part: I don't use Sanity alone. I use Sanity + Supabase.
Why? Because Sanity excels at managing structured content, but Supabase is better for complex queries and real-time searches.
My architecture:
1. Sanity is the source of truth for all gestoría information 2. Supabase has a synced copy of the data optimized for searches 3. Next.js queries Supabase for fast searches, and Sanity when it needs enriched content
On the frontend, when a user searches "gestorías near me", I query Supabase:
```javascript // lib/supabase.js const { data, error } = await supabase .from('gestorías') .select('*') .eq('codigoPostal', userPostalCode) .order('rating', { ascending: false })
// Then fetch full details from Sanity const detalles = await sanityClient.fetch(` *[_type == "gestoria" && slug.current in $slugs] `, { slugs: data.map(g => g.slug) }) ```
This gives me the best of both worlds: fast searches + rich content.
What I Learned (The Hard Things)
1. Synchronization is Your Silent Enemy
When you have two data sources (Sanity and Supabase), synchronization becomes your number one problem. If an editor updates a gestoría in Sanity but Supabase doesn't know about it, users see stale data.
Solution: Webhooks. Sanity fires webhooks when content changes, and a serverless function on Vercel automatically updates Supabase. No manual intervention.
2. Data Validation is Critical
Not all incoming data is correct. Invalid postal codes, duplicate services, locations without coordinates.
In Sanity, I created custom validations that prevent editors from publishing broken data:
```javascript validation: Rule => Rule.custom(async (value) => { if (!value) return true const codigoValido = await validarCodigoPostalEspania(value) return codigoValido || 'Código postal español inválido' }) ```
3. Performance Matters When You Have 900+ Documents
Early versions of my Sanity queries were slow. I was requesting all data from all gestorías, then filtering on the client.
Now I use GROQ projections to request only what I need:
```javascript // Request only the fields needed for search *[_type == "gestoria"] | order(rating desc) { _id, nombre, slug, "ubicacion": ubicacion.codigoPostal, rating, numeroResenas } ```
This significantly reduced response sizes.
Real Project Metrics
As of today:
- **900+ verified gestorías** in the database
- **71,895 real reviews** aggregated
- **Tech stack**: TypeScript 99%, built with Next.js, Sanity, Supabase
- **Recent improvements**: City name detection, mobile UX improvements (phases 9-11 recently completed)
- **Status**: Actively maintained and in development
It's not massive in terms of users, but it's a valuable exercise in:
- Content management at scale
- Complex data architecture
- Synchronization between systems
- SEO and discoverability
The Takeaway: Choose the Right Tool for the Right Problem
Sanity CMS isn't the solution for everything. But for directories, content marketplaces, or anything where you need to:
- Manage many complex documents
- Allow non-technical people to edit
- Separate content from presentation
- Scale without rewriting your frontend
...it's an exceptional tool.
What made this project different was not trying to be a hero. I didn't build a custom CMS. I didn't try to optimize prematurely. I used tools that already existed and solved the problem.
Then I focused on the only thing that matters: having accurate, accessible, and useful data for users looking for a gestoría.
That's it. No hype. Just sensible architecture.
---
Building directories or marketplaces? Data architecture is where most people fail. If you want to avoid that, think of Sanity as your first step, not your last.
