Tu buscador de programas se agota en 8.2 segundos. De nuevo. Es la semana de inscripción y el portal de estudiantes está colapsando — 40,000 estudiantes de pregrado actualizando la misma búsqueda rota, tu cola de soporte llena de tickets, tu VP de Inscripciones viendo las tasas de conversión caer en tiempo real. Los parches de seguridad de Drupal 7 terminan en seis meses. Tienes más de 200 páginas de programas, un CMS heredado que tu equipo apenas entiende, y un requisito innegociable: cero tiempo de inactividad. A principios de 2024, una gran universidad estatal nos entregó exactamente este problema. Necesitaban una migración completa a Next.js antes de que Drupal 7 se apagara — y necesitaban que su buscador de programas dejara de perder estudiantes. Aquí está cómo reconstruimos todo su portal en 26 semanas, redujimos el tiempo de respuesta de búsqueda a 340ms, e implementamos sin tomar el sitio fuera de línea ni un solo minuto.

Esta es la historia de cómo migramos todo a Next.js con un backend de CMS headless, redujimos los tiempos de carga de página en 73%, y lo entregamos a tiempo. Compartiré las decisiones arquitectónicas que tomamos (y las que casi cometemos), el proceso real de migración, los puntos de referencia de rendimiento, y las lecciones que se aplican a cualquier migración de CMS a gran escala.

Tabla de contenidos

Case Study: Migrating a University Portal from Drupal to Next.js

El punto de partida: con qué estábamos trabajando

Déjame pintar el cuadro. La presencia digital de la universidad se construyó sobre Drupal 7, lanzado originalmente alrededor de 2014. Durante la última década, había acumulado:

  • ~12,000 nodos de contenido en programas, cursos, perfiles de profesores, artículos de noticias y eventos
  • Más de 200 páginas de programas académicos cada una con relaciones de taxonomía complejas (nivel de grado, departamento, colegio, formato de entrega, estado de acreditación)
  • Un buscador de programas personalizado construido como una búsqueda basada en Drupal Views con filtros expuestos — funcional pero lento
  • Un portal de estudiantes con acceso autenticado a herramientas de asesoría, auditorías de grado, enlaces de registro y paneles personalizados
  • 47 módulos Drupal personalizados, de los cuales 19 ya no se mantenían
  • 3 capas de tema diferentes apiladas una encima de la otra de rediseños sucesivos

El sitio estaba alojado en dos máquinas virtuales envejecidas detrás de un equilibrador de carga institucional. Durante la inscripción máxima (agosto y enero), el buscador de programas regularmente se agotaba. El equipo de marketing había recurrido a publicar una lista PDF de programas como respaldo. Eso lo dice todo.

Core Web Vitals eran ásperos:

Métrica Drupal 7 (Antes) Objetivo
LCP 6.2s < 2.5s
FID 380ms < 100ms
CLS 0.31 < 0.1
TTFB 2.8s < 0.8s
Carga del buscador de programas 8.4s < 1.5s

El panorama de partes interesadas

Los proyectos web universitarios son únicamente desafiantes debido al número de partes interesadas. Estábamos trabajando con:

  • TI Central — responsable de la integración SSO, cumplimiento de seguridad y alojamiento
  • Marketing y Comunicaciones — propietarios de la marca, estrategia de contenido y análisis
  • La oficina del registrador — propietaria de datos de programas y el sistema de información de estudiantes (SIS)
  • Colegios y departamentos individuales — cada uno con sus propios editores de contenido (más de 80 personas con acceso a CMS)
  • Gobierno estudiantil — quienes abogaban ruidosamente por un diseño mobile-first (con razón)

Obtener alineación de todos estos grupos tomó las primeras tres semanas del proyecto. Realizamos un sprint de diseño para establecer prioridades compartidas y no negociables.

Por qué Next.js (y por qué no Drupal 10)

La pregunta obvia: ¿por qué no simplemente actualizar a Drupal 10? El equipo de TI de la universidad ya había iniciado ese camino seis meses antes de contactarnos. Lo abandonaron después de descubrir que 23 de sus 47 módulos personalizados no tenían equivalente en Drupal 10 y necesitarían ser completamente reescritos.

El cálculo real parecía así:

Factor Migración a Drupal 10 Reconstrucción en Next.js
Línea de tiempo estimada 8-10 meses 6 meses
Reescrituras de módulos personalizados 23 módulos N/A (reconstruidos como APIs/componentes)
Reentrenamiento de editores de contenido Moderado (nuevo admin UI) Moderado (nuevo CMS)
Límite de rendimiento Mejora moderada Mejora dramática
Flexibilidad de alojamiento LAMP tradicional/similar Implementación en edge, CDN-first
Grupo de contratación de desarrolladores Encogiéndose (especialistas en Drupal) Creciendo (React/Next.js)
Costo anual de mantenimiento a largo plazo ~$180K/año ~$95K/año

La diferencia de costo de mantenimiento fue el cierre para la administración. Los desarrolladores de Drupal con experiencia institucional eran más difíciles de encontrar y más caros de retener. El propio equipo de TI de la universidad tenía tres desarrolladores de React y cero especialistas en Drupal después de que su desarrollador senior de Drupal se jubilara.

Elegimos Next.js específicamente (sobre Gatsby, Remix o Astro) por varias razones:

  1. Renderización híbrida — las páginas de programas podían generarse estáticamente, mientras que el portal de estudiantes necesitaba renderización del lado del servidor con autenticación
  2. Rutas API — podríamos construir middleware para la integración de SIS sin un servicio backend separado
  3. Regeneración estática incremental (ISR) — los datos del programa cambian semanalmente, no cada hora. ISR con una ventana de revalidación de 1 hora fue perfecta
  4. El equipo de la universidad conocía React — lo mantendrían después del traspaso

Si estás evaluando opciones similares, nuestra página de capacidades de desarrollo Next.js cubre los detalles técnicos de lo que normalmente construimos.

Decisiones arquitectónicas

Selección de CMS headless

Evaluamos cinco opciones de CMS headless contra los requisitos de la universidad: más de 80 editores de contenido, relaciones de contenido complejas, permisos basados en roles, y un modelo de precios razonable por puesto.

Nos decidimos por Sanity para este proyecto. Los factores clave:

  • Las consultas GROQ manejaban las relaciones de taxonomía complejas entre programas, departamentos y colegios mucho mejor que GraphQL para este caso de uso
  • Colaboración en tiempo real — múltiples editores podían trabajar simultáneamente sin conflictos
  • Componentes de entrada personalizados — construimos un mapa de requisitos previos del programa directamente en el estudio
  • Precios — el plan Enterprise a ~$949/mes estaba bien dentro del presupuesto, y el costo por usuario era predecible

La modelación de contenido tomó aproximadamente dos semanas. Definimos 14 tipos de documento y 8 tipos de referencia. El esquema del programa solo tenía 34 campos, incluyendo datos estructurados para el marcado EducationalOrganization y Course de schema.org.

Para más información sobre nuestro enfoque de arquitectura de CMS, consulta nuestra página de desarrollo de CMS headless.

Infraestructura

Implementamos en Vercel para el frontend de Next.js (el plan Enterprise, requerido para cumplimiento de FERPA y requisitos de SSO). Las rutas autenticadas del portal de estudiantes utilizan renderización del lado del servidor con gestión de sesiones a través del CAS (Central Authentication Service) SSO existente de la universidad.

El flujo de datos se ve así:

[Sanity CMS] → [Next.js en Vercel] → [CDN Edge]
                    ↕
           [API del SIS de la universidad]
                    ↕
           [CAS SSO / LDAP]

Las páginas estáticas del programa se pre-renderizan en tiempo de compilación y se revalidan cada hora mediante ISR. El buscador de programas utiliza una combinación de datos pre-obtenidos (cargados en el cliente en tiempo de compilación como índice JSON) y filtrado en tiempo real — no se necesita viaje de servidor para operaciones de búsqueda.

La capa de API

El sistema de información del estudiante (Ellucian Banner, si tienes curiosidad — siempre es Banner) expuso una API SOAP. Sí, en 2024. Construimos una capa de traducción usando rutas API de Next.js que consumían los puntos finales SOAP y exponían puntos finales REST limpios al frontend:

// /app/api/programs/[programId]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { fetchFromBanner } from '@/lib/banner-client';
import { transformProgramData } from '@/lib/transforms';

export async function GET(
  request: NextRequest,
  { params }: { params: { programId: string } }
) {
  const bannerData = await fetchFromBanner(
    'PROGRAM_DETAIL',
    { programCode: params.programId }
  );
  
  const program = transformProgramData(bannerData);
  
  return NextResponse.json(program, {
    headers: {
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
    },
  });
}

Esta capa de traducción fue una de las piezas de mayor valor del proyecto. Desacoplaba el frontend de las peculiaridades de Banner y le daba a la universidad una API limpia que podían usar para proyectos futuros (una aplicación móvil ya estaba siendo discutida).

Case Study: Migrating a University Portal from Drupal to Next.js - architecture

El buscador de programas: reconstruyendo la característica principal

El buscador de programas fue la página más importante en todo el sitio. Los análisis mostraban que representaba el 34% de todo el tráfico de búsqueda orgánica y era el punto de entrada #1 para estudiantes prospectivos. Cometer un error en esto no era una opción.

El enfoque anterior (y por qué era lento)

La versión de Drupal usaba Views con filtros expuestos. Cada cambio de filtro activaba un viaje de servidor completo, re-consultaba la base de datos, y re-renderizaba toda la página. Con más de 200 programas y 6 dimensiones de taxonomía (nivel de grado, colegio, departamento, formato de entrega, área de interés, y búsqueda de palabras clave), la consulta era cara.

El nuevo enfoque

Pre-construimos un índice de búsqueda en tiempo de compilación. Todos los 200+ programas se serializaron en un archivo JSON de ~180KB (comprimido con gzip a ~22KB) que se envía con la página. El filtrado ocurre completamente en el lado del cliente usando un hook personalizado:

// hooks/useProgramSearch.ts
import { useMemo, useState } from 'react';
import Fuse from 'fuse.js';
import { Program, ProgramFilters } from '@/types';

const fuseOptions = {
  keys: [
    { name: 'title', weight: 0.4 },
    { name: 'description', weight: 0.2 },
    { name: 'keywords', weight: 0.3 },
    { name: 'department', weight: 0.1 },
  ],
  threshold: 0.3,
};

export function useProgramSearch(programs: Program[]) {
  const [filters, setFilters] = useState<ProgramFilters>({});
  const fuse = useMemo(() => new Fuse(programs, fuseOptions), [programs]);

  const results = useMemo(() => {
    let filtered = programs;

    if (filters.degreeLevel) {
      filtered = filtered.filter(p => p.degreeLevel === filters.degreeLevel);
    }
    if (filters.college) {
      filtered = filtered.filter(p => p.college === filters.college);
    }
    if (filters.deliveryFormat) {
      filtered = filtered.filter(p => 
        p.deliveryFormats.includes(filters.deliveryFormat!)
      );
    }
    if (filters.searchQuery) {
      const fuseResults = fuse.search(filters.searchQuery);
      const fuseIds = new Set(fuseResults.map(r => r.item.id));
      filtered = filtered.filter(p => fuseIds.has(p.id));
    }

    return filtered;
  }, [programs, filters, fuse]);

  return { results, filters, setFilters };
}

Usamos Fuse.js para búsqueda de texto difusa y filtrado de JavaScript simple para facetas. El resultado: los resultados de búsqueda aparecen en menos de 50ms. Sin spinners de carga. Sin llamadas al servidor. Los usuarios pueden golpear los filtros tan rápido como quieran.

Cada resultado del programa vincula a una página de detalle generada estáticamente con marcado schema.org completo, lo que mejoró dramáticamente la aparición de la universidad en las características de búsqueda relacionadas con educación de Google.

Migración del portal de estudiantes

El portal de estudiantes fue la parte más complicada. Requería autenticación, personalización, y datos en tiempo real de Banner. No podríamos generar estáticamente ninguna parte.

Flujo de autenticación

La universidad usa CAS para inicio de sesión único en todos los sistemas institucionales. Integramos CAS con Next.js usando un flujo de autenticación personalizado:

  1. Usuario no autenticado accede /portal → redirigido al inicio de sesión CAS
  2. CAS redirige de vuelta con un boleto de servicio
  3. Nuestra ruta de API valida el boleto contra el servidor CAS
  4. Creamos un JWT firmado almacenado en una cookie httpOnly
  5. Las solicitudes posteriores usan el JWT para gestión de sesión

Usamos next-auth (ahora Auth.js) con un proveedor de CAS personalizado que escribimos desde cero, ya que ningún proveedor de CAS mantenido existía en ese momento.

Características del portal

El portal de estudiantes incluía:

  • Panel personalizado con fechas de registro próximas, restricciones, e información del asesor
  • Resumen de auditoría de grado extraído de Banner en tiempo real
  • Enlaces rápidos al LMS (Canvas), correo electrónico, y sistemas de biblioteca
  • Recursos específicos del programa basados en la especialidad declarada del estudiante

Todas las páginas del portal usan renderización del lado del servidor. Cacheamos las respuestas de la API de Banner agresivamente (TTL de 30 segundos para la mayoría de endpoints, TTL de 5 minutos para auditorías de grado) para evitar sobrecargar su sistema.

Estrategia de migración de contenido

Migrar 12,000 nodos de contenido de Drupal a Sanity requería un enfoque sistemático. Construimos un canal de migración personalizado:

# Canal de migración simplificado
1. Exportar nodos de Drupal → JSON vía comandos Drush personalizados
2. Transformar JSON → formato de documento de Sanity vía scripts de Node.js
3. Procesar archivos de medios → cargar a CDN de Sanity
4. Importar documentos → API de migración de Sanity
5. Validar → comprobaciones automatizadas para referencias rotas

La migración de medios fue la parte más tediosa. La gestión de archivos de Drupal almacena archivos con rutas internas y referencias de base de datos. Escribimos un script que:

  1. Descargó cada archivo del directorio de archivos de Drupal
  2. Lo cargó en el canal de activos de Sanity
  3. Mapeó ID de archivos antiguos de Drupal a referencias de activos nuevas de Sanity
  4. Actualizó todo el contenido de texto enriquecido para apuntar a las nuevas referencias de activos

Este script se ejecutó durante aproximadamente 14 horas en el conjunto de datos completo. Lo ejecutamos tres veces durante el proyecto: una para pruebas iniciales, una en el punto medio para refrescar el staging, y una para el cutover final.

Estrategia de congelación de contenido

Implementamos una congelación de contenido en dos fases:

  • Semanas 1-20: Los editores de contenido trabajan en Drupal como de costumbre. Migramos snapshots al staging semanalmente.
  • Semanas 21-23: Entrada dual. El contenido nuevo va tanto a Drupal como a Sanity. Editores entrenados en nuevo CMS.
  • Semana 24: Cutover completo. Drupal se vuelve de solo lectura, luego se apaga.

El período de entrada dual fue doloroso pero necesario. Teníamos más de 80 editores, y necesitaban desarrollar memoria muscular con Sanity antes de que fuera su única opción.

La línea de tiempo de 6 meses

Mes Fase Entregables clave
Mes 1 Descubrimiento y arquitectura Alineación de partes interesadas, selección de CMS, configuración de infraestructura, modelación de contenido
Mes 2 Desarrollo principal Sistema de diseño, plantillas de página, páginas de detalle del programa, navegación
Mes 3 Buscador de programas y búsqueda Índice de búsqueda, UI de filtrado, canal de datos del programa, marcado SEO
Mes 4 Portal de estudiantes Integración CAS, capa de API de Banner, panel de control, visualización de auditoría de grado
Mes 5 Migración de contenido y entrenamiento Scripts de migración, entrenamiento de editores (6 sesiones), QA de staging
Mes 6 QA, rendimiento, lanzamiento Pruebas de carga, auditoría de accesibilidad, congelación de contenido, cutover de DNS

Nuestro equipo era 4 desarrolladores, 1 diseñador, y 1 gerente de proyecto. La universidad proporcionó un propietario de producto dedicado más un enlace de TI para el trabajo de integración de Banner/CAS.

Nos encontramos con dos contratiempos importantes:

  1. Mes 3: La API SOAP de Banner tenía un límite de velocidad no documentado de 100 solicitudes/minuto. Nuestro buscador de programas fue diseñado para obtener en lote todos los datos del programa durante la compilación. Tuvimos que implementar un sistema de cola y extender la compilación en múltiples lotes.

  2. Mes 5: La auditoría de accesibilidad encontró 34 violaciones de WCAG 2.1 AA. La mayoría fueron heredadas del diseño (contraste de color insuficiente en botones secundarios, indicadores de enfoque faltantes en los filtros del buscador de programas). Pasamos 8 días no planeados en remediación.

Resultados de rendimiento

Aquí está cómo se veían los números después del lanzamiento:

Métrica Drupal 7 (Antes) Next.js (Después) Mejora
LCP 6.2s 1.1s 82% más rápido
FID / INP 380ms 45ms 88% más rápido
CLS 0.31 0.02 94% mejor
TTFB 2.8s 0.12s 96% más rápido
Carga del buscador de programas 8.4s 0.8s 90% más rápido
Puntuación de Lighthouse 34 97 +63 puntos
Tiempo de compilación (completo) N/A 4m 12s
Costo de alojamiento mensual ~$2,400 ~$1,100 54% menor

Pero los números que más importaban para la universidad eran estos:

  • El uso del buscador de programas aumentó 156% en el primer semestre después del lanzamiento
  • La tasa de rebote móvil cayó de 67% a 31%
  • El tráfico de búsqueda orgánica a páginas de programas aumentó 43% dentro de 4 meses (marcado schema.org + mejoras de Core Web Vitals)
  • Los tickets de soporte relacionados con el portal cayeron 62% — en gran parte porque las páginas realmente se cargaban de manera confiable
  • Cero tiempo de inactividad durante la inscripción de otoño — la primera vez en tres años

Lecciones aprendidas

1. Inicia la integración de CAS/SSO temprano

Programamos la integración de CAS para el mes 4. Deberíamos haber iniciado una prueba de concepto en el mes 1. Los equipos de TI universitarios se mueven deliberadamente (lee: lentamente) a través de revisiones de seguridad. Obtener la aprobación de la arquitectura de SSO tomó tres semanas de ida y vuelta con su oficina de seguridad.

2. La modelación de contenido es arquitectura

Pasamos dos semanas completas en modelación de contenido antes de escribir ningún código frontend. Esto parecía lento en ese momento. Fue la inversión individual más valiosa que hicimos. Cuando tienes 200+ programas con relaciones complejas entre departamentos, colegios, niveles de grado, concentraciones, y formatos de entrega, obtener el esquema correcto desde el principio te ahorra cientos de horas de refactorización.

3. Entrena a los editores temprano, no solo antes del lanzamiento

Originalmente planeamos el entrenamiento de editores para el mes 5. Lo movimos al mes 4 después de la retroalimentación del propietario del producto. Esto dio a los editores seis semanas para sentirse cómodos con Sanity en lugar de dos. La calidad del contenido ingresado durante el período de entrada dual fue dramáticamente mejor gracias a esto.

4. Banner es Banner

Si trabajas con Ellucian Banner (y si estás en educación superior, probablemente lo hagas), presupuesta tiempo extra para la integración de API. La documentación es escasa, los puntos finales SOAP son inconsistentes, y cada institución ha personalizado su instancia de Banner diferentemente. Nuestra capa de traducción fue esencial.

5. Presupuesta accesibilidad desde el primer día

Nuestras 34 violaciones de WCAG en el mes 5 fueron casi completamente evitables. Ahora ejecutamos comprobaciones de axe-core en nuestro canal de CI en cada solicitud de extracción. Si estás construyendo para una universidad pública, el cumplimiento de WCAG 2.1 AA no es opcional — es un requisito legal bajo la Sección 508.

Si enfrentas un desafío de migración similar, estamos felices de hablar a través de los detalles. Puedes contactarnos directamente o consultar nuestra página de precios para cómo típicamente alcanzamos estos proyectos.

FAQ

¿Cuánto tiempo toma migrar un sitio web universitario de Drupal a Next.js?

Para un sitio de esta escala — 12,000 nodos de contenido, 200+ programas, portal autenticado de estudiantes — seis meses es realista con un equipo dedicado de 4-6 personas. Los sitios institucionales más pequeños (menos de 2,000 páginas, sin portal) a menudo pueden hacerse en 3-4 meses. La línea de tiempo es impulsada menos por la compilación del frontend y más por la migración de contenido, alineación de partes interesadas, e integración con sistemas institucionales como Banner o PeopleSoft.

¿Cuál es el mejor CMS headless para sitios web de educación superior?

Depende del tamaño del equipo editorial y la comodidad técnica. Elegimos Sanity para este proyecto por su colaboración en tiempo real, modelación flexible de contenido, y lenguaje de consulta GROQ. Contentful y Storyblok también son opciones fuertes. Para universidades con equipos de contenido muy grandes (100+ editores), el modelo de flujo de trabajo y permisos de Contentful puede ser ventajoso. Para equipos más pequeños que desean más personalización, Sanity tiende a ganar.

¿Puede Next.js manejar portales autenticados de estudiantes?

Absolutamente. Next.js admite renderización del lado del servidor para páginas autenticadas, y los componentes del servidor del App Router hacen que sea directo obtener datos específicos del usuario sin exponerlos al paquete del cliente. Integramos con CAS (Central Authentication Service) usando Auth.js con un proveedor personalizado. El portal manejó 40,000 estudiantes sin problemas de rendimiento.

¿Cuánto cuesta una migración de Drupal a Next.js para una universidad?

Un proyecto de este alcance — buscador de programas, portal de estudiantes, 200+ programas, migración completa de contenido, configuración de CMS, y entrenamiento — típicamente oscila entre $250,000 y $450,000 dependiendo de la complejidad. Sin embargo, los ahorros a largo plazo son significativos. Esta universidad redujo sus costos anuales de mantenimiento de aproximadamente $180K a $95K, significando que el proyecto se paga a sí mismo dentro de 3-4 años incluso en el extremo superior del rango de presupuesto.

¿Qué sucede con el SEO durante una migración de CMS a gran escala?

Esta es una preocupación legítima. Implementamos un mapa de redirección completo (más de 2,400 redirecciones 301), preservamos todas las estructuras de URL existentes donde fue posible, y agregamos datos estructurados de schema.org que el sitio de Drupal carecía. El tráfico orgánico bajó aproximadamente 8% en las primeras dos semanas post-lanzamiento (normal para cualquier migración importante), luego se recuperó y exceló la línea base en 43% dentro de cuatro meses.

¿Es Drupal 10 una mejor opción que volverse headless para universidades?

Puede serlo, dependiendo de la situación. Si tu equipo tiene experiencia fuerte en Drupal, tus módulos personalizados tienen compatibilidad con Drupal 10, y no necesitas las características de rendimiento de un sitio estático/híbrido, Drupal 10 es un camino perfectamente válido. En nuestro caso, la universidad había perdido su experiencia en Drupal, tenía 23 módulos incompatibles, y necesitaba mejoras dramáticas de rendimiento. El enfoque headless era claramente el mejor ajuste.

¿Cómo manejas la migración de contenido de Drupal a un CMS headless?

Usamos scripts personalizados de Node.js que exportan contenido de Drupal vía comandos Drush, transforman los datos para coincidir con el nuevo esquema de CMS, manejan migración de archivos de medios, e importan todo vía la API de migración del CMS. El proceso típicamente se ejecuta 3 veces: una para pruebas iniciales, una para actualización de staging, y una para cutover final. El contenido de texto enriquecido con medios incrustados es la parte más difícil — necesitas remapear cada referencia de archivo interna.

¿Puedes ejecutar Drupal y Next.js simultáneamente durante la migración?

Sí, y lo recomendamos. Durante nuestra migración, Drupal continuó sirviendo el sitio de producción mientras construíamos y probábamos la versión de Next.js en un dominio de staging. Usamos un período de entrada dual de tres semanas donde el contenido iba a ambos sistemas. El cutover final fue un cambio de DNS que tomó alrededor de 15 minutos, con Drupal mantenido en modo de solo lectura durante 30 días como respaldo.