Traducir artículo de markdown al español

A principios de 2024, una gran universidad estatal se acercó a nosotros con un problema que se ha vuelto dolorosamente común en la educación superior: su instalación de Drupal 7 estaba llegando al final de su vida útil, su portal de estudiantes se estaba desmoronando bajo carga durante los períodos de inscripción, y su buscador de programas — la herramienta de conversión más importante de su sitio web — tardaba más de 8 segundos en devolver resultados de búsqueda. Tenían 40,000 estudiantes activos, más de 200 programas académicos, y una ventana de seis meses antes de que el soporte de seguridad de Drupal 7 efectivamente terminara. Sin presión.

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 un 73%, y lo pusimos en marcha según lo programado. Compartiré las decisiones de arquitectura que tomamos (y las que casi nos equivocamos), el proceso de migración real, puntos de referencia de rendimiento, y las lecciones que se aplican a cualquier migración de CMS a gran escala.

Tabla de contenidos

Estudio de caso: Migración de un portal universitario de Drupal a Next.js

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

Déjame pintar el panorama. La presencia digital de la universidad fue construida en Drupal 7, originalmente lanzada alrededor de 2014. Durante la pasada década, había acumulado:

  • ~12,000 nodos de contenido entre programas, cursos, perfiles de facultad, artículos de noticias y eventos
  • 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 asesoramiento, auditorías de grados, enlaces de registro, y tableros personalizados
  • 47 módulos Drupal personalizados, de los cuales 19 ya no se mantenían
  • 3 capas de tema diferentes apiladas una sobre la otra de rediseños sucesivos

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

Los 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 por el número de partes interesadas. Trabajamos con:

  • IT Central — responsable de la integración de SSO, cumplimiento de seguridad y alojamiento
  • Marketing y Comunicaciones — propietarios de la marca, estrategia de contenido y análisis
  • La oficina del registrador — propietarios 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 al CMS)
  • Gobierno estudiantil — que abogaba fuertemente por un diseño mobile-first (con razón)

Lograr alineación de todos estos grupos tomó las primeras tres semanas del proyecto. Realizamos un design sprint para establecer prioridades compartidas e inexorables.

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

La pregunta obvia: ¿por qué no simplemente actualizar a Drupal 10? El equipo de IT de la universidad había comenzado ese camino seis meses antes de contactarnos. 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 se veía así:

Factor Migración de Drupal 10 Reconstrucción de 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 (nueva interfaz de administración) Moderado (nuevo CMS)
Techo de rendimiento Mejora moderada Mejora dramática
Flexibilidad de alojamiento LAMP tradicional/similar Despliegue de borde, CDN-first
Grupo de contratación de desarrolladores Reduciendo (especialistas en Drupal) Creciendo (React/Next.js)
Costo de mantenimiento a largo plazo ~$180K/año ~$95K/año

La diferencia en el costo de mantenimiento fue el decisor para la administración. Los desarrolladores de Drupal con experiencia institucional eran cada vez más difíciles de encontrar y más caros de retener. El equipo de IT 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. Renderizado híbrido — las páginas de programas podrían ser generadas estáticamente, mientras que el portal de estudiantes necesitaba renderizado 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 era perfecto
  4. El equipo de la universidad conocía React — lo mantendrían después del traspaso

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

Decisiones de arquitectura

Selección de CMS headless

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

Aterrizamos en Sanity para este proyecto. Los factores clave:

  • Consultas GROQ manejaron 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 podrían trabajar simultáneamente sin conflictos
  • Componentes de entrada personalizados — construimos un mapeador de requisitos previos de programa directamente en el estudio
  • Precios — el plan Enterprise a ~$949/mes estaba dentro del presupuesto, y el costo por usuario era predecible

El modelado de contenido tomó aproximadamente dos semanas. Definimos 14 tipos de documentos y 8 tipos de referencias. El esquema del programa solo tenía 34 campos, incluyendo datos estructurados para marcado de schema.org EducationalOrganization y Course.

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

Infraestructura

Desplegamos 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 usan renderizado del lado del servidor con gestión de sesión a través del CAS (Servicio de autenticación centralizado) SSO existente de la universidad.

El flujo de datos se ve así:

[Sanity CMS] → [Next.js on Vercel] → [CDN Edge]
                    ↕
           [University SIS API]
                    ↕
           [CAS SSO / LDAP]

Las páginas de programas estáticas se pre-renderizan en tiempo de construcción y se revalidan cada hora vía ISR. El buscador de programas usa una combinación de datos pre-obtenidos (cargados en el cliente en tiempo de construcción como un índice JSON) y filtrado en tiempo real — ninguna llamada de servidor necesaria para operaciones de búsqueda.

La capa API

El sistema de información de estudiantes (Ellucian Banner, si tienes curiosidad — siempre es Banner) exponía una API SOAP. Sí, en 2024. Construimos una capa de traducción usando rutas API de Next.js que consumía los puntos finales SOAP y exponía 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. Desacopló el frontend de las peculiaridades de Banner y dio a la universidad una API limpia que podrían usar para proyectos futuros (una aplicación móvil ya estaba siendo discutida).

Estudio de caso: Migración de un portal universitario de Drupal a Next.js - arquitectura

El buscador de programas: Reconstruyendo la característica principal

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

El enfoque antiguo (y por qué era lento)

La versión de Drupal usaba Views con filtros expuestos. Cada cambio de filtro desencadenaba una ronda de servidor completa, volvía a consultar la base de datos, y re-renderizaba la página completa. 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 costosa.

El nuevo enfoque

Pre-construimos un índice de búsqueda en tiempo de construcción. Los 200+ programas fueron serializados en un archivo JSON de ~180KB (comprimido a ~22KB) que se envía con la página. El filtrado ocurre completamente del 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 JavaScript plano para facetas. El resultado: los resultados de búsqueda aparecen en menos de 50ms. Ningún spinner de carga. Ninguna llamada de 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 de 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 podíamos generar estáticamente ninguno de ellos.

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 golpea /portal → redirigido al inicio de sesión de CAS
  2. CAS redirige con un ticket de servicio
  3. Nuestra ruta API valida el ticket 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 CAS personalizado que escribimos desde cero, ya que ningún proveedor CAS mantenido existía en ese momento.

Características del portal

El portal de estudiantes incluía:

  • Tablero personalizado con fechas de registro próximas, retenciones, e información del asesor
  • Resumen de auditoría de grados extraído de Banner en tiempo real
  • Enlaces rápidos a 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 renderizado del lado del servidor. Cacheamos las respuestas de la API de Banner agresivamente (TTL de 30 segundos para la mayoría de puntos finales, TTL de 5 minutos para auditorías de grados) para evitar abrumar 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 pipeline de migración personalizado:

# Pipeline 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 → verificaciones 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 pipeline de activos de Sanity
  3. Mapeó los IDs de archivo antiguos de Drupal a nuevas referencias de activos de Sanity
  4. Actualizó todos los contenidos 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 vez para pruebas iniciales, una vez en el punto medio para actualizar el staging, y una vez para el cutover final.

Estrategia de congelamiento de contenido

Implementamos un congelamiento de contenido de dos fases:

  • Semanas 1-20: Los editores de contenido trabajan en Drupal normalmente. Migramos instantáneas al staging semanalmente.
  • Semanas 21-23: Entrada dual. El contenido nuevo va a Drupal y Sanity. Editores entrenados en nuevo CMS.
  • Semana 24: Cutover completo. Drupal se vuelve de solo lectura, luego offline.

El período de entrada dual fue doloroso pero necesario. Teníamos 80+ editores, y necesitaban construir 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, modelado de contenido
Mes 2 Desarrollo principal Sistema de diseño, plantillas de página, páginas de detalle de programas, navegación
Mes 3 Buscador y búsqueda de programas Índice de búsqueda, interfaz de filtrado, pipeline de datos de programas, marcado de SEO
Mes 4 Portal de estudiantes Integración de CAS, capa de API de Banner, tablero, visualización de auditoría de grados
Mes 5 Migración de contenido y capacitación Scripts de migración, capacitación de editores (6 sesiones), QA de staging
Mes 6 QA, rendimiento, lanzamiento Pruebas de carga, auditoría de accesibilidad, congelamiento de contenido, cutover de DNS

Nuestro equipo fue 4 desarrolladores, 1 diseñador, y 1 gestor de proyecto. La universidad proporcionó un dueño de producto dedicado más una liaison de IT para el trabajo de integración de Banner/CAS.

Nos encontramos con dos obstáculos principales:

  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 construcción. Tuvimos que implementar un sistema de colas y distribuir la construcción entre 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 planificados en remediación.

Resultados de rendimiento

Aquí está lo que los números se veían 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 construcción (completo) N/A 4m 12s
Costo de alojamiento mensual ~$2,400 ~$1,100 54% menor

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

  • El uso del buscador de programas aumentó un 156% en el primer semestre después del lanzamiento
  • La tasa de rebote móvil cayó del 67% al 31%
  • El tráfico de búsqueda orgánica a las páginas de programas aumentó un 43% dentro de 4 meses (marcado de schema.org + mejoras de Core Web Vitals)
  • Los tickets de soporte relacionados con el portal disminuyeron un 62% — en gran medida 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. Comenzar la integración de CAS/SSO temprano

Programamos la integración de CAS para el mes 4. Debería haber comenzado una prueba de concepto en el mes 1. Los equipos de IT universitarios se mueven deliberadamente (léase: lentamente) a través de revisiones de seguridad. Obtener aprobación de la arquitectura de SSO tomó tres semanas de intercambios con su oficina de seguridad.

2. El modelado de contenido es arquitectura

Pasamos dos semanas completas en modelado de contenido antes de escribir cualquier código frontend. Esto se sintió 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 de antemano ahorra cientos de horas de refactorización.

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

Inicialmente planeamos la capacitación de editores para el mes 5. La movimos al mes 4 después de la retroalimentación del dueño 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 estás trabajando con Ellucian Banner (y si estás en educación superior, probablemente lo estés), presupuesta tiempo extra para la integración de la API. La documentación es escasa, los puntos finales SOAP son inconsistentes, y cada institución ha personalizado su instancia de Banner de manera diferente. Nuestra capa de traducción fue esencial.

5. Presupuesta para accesibilidad desde el primer día

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

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

Preguntas frecuentes

¿Cuánto tiempo tarda migrar un sitio web universitario de Drupal a Next.js? Para un sitio de esta escala — 12,000 nodos de contenido, 200+ programas, portal de estudiantes autenticado — 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 está impulsada menos por la construcció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, modelado de contenido flexible, 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 deseen más personalización, Sanity tiende a ganar.

¿Puede Next.js manejar portales de estudiantes autenticados? Absolutamente. Next.js soporta renderizado del lado del servidor para páginas autenticadas, y los componentes del servidor del App Router hacen directo obtener datos específicos del usuario sin exponerlo al bundle del cliente. Integramos con CAS (Servicio de autenticación centralizado) 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 de contenido completa, configuración de CMS, y capacitación — 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 por sí mismo dentro de 3-4 años incluso en el extremo superior del rango de presupuesto.

¿Qué pasa con SEO durante una migración de CMS a gran escala? Esta es una preocupación legítima. Implementamos un mapa de redireccionamiento 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 cayó aproximadamente 8% en las primeras dos semanas post-lanzamiento (normal para cualquier migración importante), luego se recuperó y superó la línea de base en un 43% dentro de cuatro meses.

¿Es Drupal 10 una opción mejor que ir 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 una ruta perfectamente válida. 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 la 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 vez para pruebas iniciales, una vez para actualización de staging, y una vez 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 construimos y probamos 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ó aproximadamente 15 minutos, con Drupal mantenido en modo de solo lectura durante 30 días como respaldo.