Sanity CMS for 1,300+ Gestorías: How I Managed Thousands of Business Listings Without Losing My Mind

Projects· 5 min read

The Problem: A Thousand Businesses, One Database, Zero Structure

When I started Gestorías Cerca de Mí, I had a classic problem every directory faces: how do I manage 1,300+ businesses without requiring a deploy for every change?

My first instinct was to use Supabase directly. I had the database, I had Next.js, I had Vercel. What else did I need?

Answer: a lot more.

The reality is that a directory isn't just data. You need:

  • Consistent descriptions
  • Optimized images
  • Related fields (services, specialties, hours)
  • Version control for changes
  • A human way to edit everything

If you try to do it with just SQL, you end up with:

  • A Next.js admin panel nobody wants to maintain
  • Free-text fields where there should be structure
  • Unoptimized images
  • Fear of making changes

Why I Chose Sanity CMS (and Not Something Else)

I evaluated three options:

1. Strapi: Open source, self-hosted. Pretty in theory, heavy in practice.

2. Contentful: Powerful but expensive. For 1,300+ gestorías, pricing scaled quickly.

3. Sanity: Headless, flexible, with pricing that doesn't kill you.

I chose Sanity because:

Portable Text for Rich Descriptions

Each gestoría has a description. Some businesses want plain text, some want lists, some want internal links to specific services.

With Sanity, I use Portable Text (their structured content format). In my stack you see the dependency:

```json "@portabletext/react": "^3.0.0" ```

This lets me keep content flexible without being chaotic:

```typescript import { PortableText } from '@portabletext/react';

export function GestoriaDescription({ content }) { return <PortableText value={content} />; } ```

The editor (or me) can create rich descriptions without touching code.

Automatically Optimized Images

I have this dependency in my project:

```json "@sanity/image-url": "^1.0.0" ```

Sanity handles images intelligently. I upload a 5MB photo, and it automatically generates optimized versions. In my code:

```typescript import imageUrlBuilder from '@sanity/image-url';

const builder = imageUrlBuilder(sanityClient);

function urlFor(source) { return builder.image(source); } ```

Then in Next.js:

```typescript <Image src={urlFor(gestorias.logo).width(400).url()} alt={gestorias.nombre} width={400} height={300} /> ```

Without this, I would have ended up with unoptimized images, slow loading, killing my SEO.

Real-time Queries

My stack uses `@sanity/client` to fetch data in real-time:

```json "@sanity/client": "^6.0.0" ```

In my API routes or in `getStaticProps`:

```typescript import { client } from '@/sanity/client';

export async function getStaticProps({ params }) { const gestorias = await client.fetch(` *[_type == "gestorias" && slug.current == $slug][0] `, { slug: params.slug });

return { props: { gestorias }, revalidate: 3600 // ISR every hour }; } ```

This is Incremental Static Regeneration. I generate static pages for SEO, but automatically revalidate them when content changes in Sanity.

Current Architecture (Real Commits)

Looking at my recent GitHub commits:

```

  • feat: implement Phases 9-11 mobile UX improvements (2f8d5c3, 12/30/2025)
  • feat: add city name detection to search functionality (57421d2, 12/30/2025)
  • feat(skills): add 8 Claude Code skills for improved implementation quality (c6a5887, 12/30/2025)

```

I'm in continuous improvement phase. The last 3 weeks I've focused on:

1. Automatic city detection: When you search "Madrid", the system understands it's a city, not a random word. 2. Mobile UX: 71,895 reviews and 1,307 gestorías—everything must work on mobile. 3. Claude Code Skills: I use Claude to maintain implementation quality.

Real Numbers

Today, Gestorías Cerca de Mí has:

  • **1,307 verified gestorías**
  • **71,895 real reviews**
  • **99% TypeScript stack** (code quality)
  • **0 open issues** (clean backlog)
  • **Last update**: 2 days ago

That doesn't happen by accident. It happens because:

1. Sanity manages content at scale 2. Supabase handles transactional data (reviews, searches) 3. Next.js + Vercel handle fast delivery 4. Each technology does one thing well

The Real Cost

Someone will ask: how much does Sanity cost?

My plan: Free tier (up to 10,000 documents).

I have 1,307 gestorías. I'm comfortable in the free tier.

When I scale, I'll pay. But today: $0 on Sanity.

Compared to Contentful ($89/month minimum), that's $1,068 annually I don't spend.

What I Learned

1. A Headless CMS Isn't Luxury, It's Necessity

When you have more than 100 listings, you need to separate content from code. Period.

2. Portable Text Is Underrated

Most CMS give you free-text fields. Sanity gives you structure without rigidity. It's the sweet spot.

3. ISR Is Your Friend

I don't need to revalidate everything every hour. I revalidate only when content changes. That's efficiency.

4. Optimized Images Matter

My Core Web Vitals jumped 20 points when I optimized images with Sanity. It's not vanity—it's SEO.

What's Next

I'm working on:

  • **Postal code search**: Automatically detect user's zone
  • **Advanced filters**: By services, prices, specialties
  • **Real-time review integration**: Pull reviews from Google and other sites

All this is possible because Sanity gives me structure. If I used raw SQL, I'd be writing migrations.

The Takeaway

Don't build a directory with just a database. Use a headless CMS.

Not because it's trendy. Because you need to separate who edits content from who edits code.

Sanity isn't the only option. But it's a good one if you:

  • Have hundreds of listings
  • Need optimized images
  • Want structured content
  • Don't want to spend $1,000+ annually

If you build something similar, try it. Sanity's free tier is generous.

Questions? You can see the code on GitHub. Everything is public.