Seu localizador de programas expira em 8,2 segundos. De novo. É semana de matrícula e o portal de alunos está desabando — 40.000 calouros atualizando a mesma busca quebrada, sua fila de suporte preenchendo com tickets, seu VP de Matrículas observando as taxas de conversão caindo em tempo real. As correções de segurança do Drupal 7 terminam em seis meses. Você tem 200+ páginas de programa, um CMS legado que sua equipe mal compreende, e um requisito inegociável: zero downtime. No início de 2024, uma grande universidade estadual nos entregou exatamente esse problema. Eles precisavam de uma migração completa para Next.js antes do Drupal 7 ficar obsoleto — e precisavam que seu localizador de programas parasse de perder alunos. Aqui está como reconstruímos todo o portal em 26 semanas, reduzimos o tempo de resposta da busca para 340ms, e implementamos sem desligar o site por um único minuto.

Esta é a história de como migramos tudo para Next.js com um backend CMS headless, reduzimos os tempos de carregamento de página em 73%, e entregamos conforme o cronograma. Vou compartilhar as decisões de arquitetura que tomamos (e as que quase erramos), o processo real de migração, benchmarks de desempenho, e as lições que se aplicam a qualquer migração CMS em larga escala.

Índice

Estudo de Caso: Migrando um Portal Universitário de Drupal para Next.js

O Ponto de Partida: Com o Que Estávamos Trabalhando

Deixe-me pintar o quadro. A presença digital da universidade foi construída no Drupal 7, originalmente lançada por volta de 2014. Na última década, havia acumulado:

  • ~12.000 nós de conteúdo em programas, cursos, perfis de professores, artigos de notícias e eventos
  • 200+ páginas de programa acadêmico cada uma com relacionamentos de taxonomia complexos (nível de diploma, departamento, faculdade, formato de entrega, status de acreditação)
  • Um localizador de programa personalizado construído como uma busca baseada em Views do Drupal com filtros expostos — funcional mas lento
  • Um portal de alunos com acesso autenticado a ferramentas de aconselhamento, auditorias de diploma, links de registro e dashboards personalizados
  • 47 módulos Drupal personalizados, dos quais 19 não eram mais mantidos
  • 3 camadas de tema diferentes empilhadas uma sobre a outra de redesigns sucessivos

O site foi hospedado em duas máquinas virtuais envelhecidas atrás de um balanceador de carga institucional. Durante matrículas em pico (agosto e janeiro), o localizador de programas regularmente expiraria. O time de marketing havia recorrido a postar uma lista de programas em PDF como backup. Isso diz tudo.

Os Core Web Vitals eram ásperos:

Métrica Drupal 7 (Antes) Meta
LCP 6.2s < 2.5s
FID 380ms < 100ms
CLS 0.31 < 0.1
TTFB 2.8s < 0.8s
Carregamento do Localizador de Programas 8.4s < 1.5s

A Paisagem de Stakeholders

Projetos web universitários são desafiadores de forma única por causa do número de stakeholders. Estávamos trabalhando com:

  • TI Central — responsável por integração SSO, conformidade de segurança e hospedagem
  • Marketing & Comunicações — eram proprietários da marca, estratégia de conteúdo e análise
  • Escritório do Registrador — era proprietário dos dados de programa e do sistema de informações de alunos (SIS)
  • Faculdades & Departamentos Individuais — cada um com seus próprios editores de conteúdo (mais de 80 pessoas com acesso CMS)
  • Governo Estudantil — que defendia fortemente design mobile-first (com razão)

Obter alinhamento de todos esses grupos levou as primeiras três semanas do projeto. Executamos um design sprint para estabelecer prioridades compartilhadas e não-negociáveis.

Por Que Next.js (E Por Que Não Drupal 10)

A questão óbvia: por que não apenas atualizar para Drupal 10? O time de TI da universidade havia realmente começado por esse caminho seis meses antes de nos contactar. Eles abandonaram depois de descobrir que 23 de seus 47 módulos personalizados não tinham equivalente em Drupal 10 e precisariam ser completamente reescritos.

O cálculo real se parecia com isto:

Fator Migração Drupal 10 Reconstrução Next.js
Cronograma estimado 8-10 meses 6 meses
Reescritas de módulos personalizados 23 módulos N/A (reconstruído como APIs/componentes)
Retreinamento de editor de conteúdo Moderado (nova UI de admin) Moderado (novo CMS)
Teto de desempenho Melhoria moderada Melhoria dramática
Flexibilidade de hospedagem LAMP tradicional/similar Deployment de borda, prioritário CDN
Pool de contratação de desenvolvedores Encolhendo (especialistas Drupal) Crescendo (React/Next.js)
Custo de manutenção a longo prazo ~$180K/ano ~$95K/ano

A diferença de custo de manutenção foi o ponto decisivo para a administração. Desenvolvedores Drupal com experiência institucional estavam ficando mais difíceis de encontrar e mais caros de reter. O próprio time de TI da universidade tinha três desenvolvedores React e zero especialistas em Drupal após seu desenvolvedor Drupal sênior se aposentar.

Escolhemos Next.js especificamente (em vez de Gatsby, Remix ou Astro) por várias razões:

  1. Renderização híbrida — páginas de programa poderiam ser estaticamente geradas, enquanto o portal de alunos precisava de renderização no servidor com autenticação
  2. Rotas de API — poderíamos construir middleware para a integração SIS sem um serviço backend separado
  3. Regeneração Estática Incremental (ISR) — dados de programa mudam semanalmente, não por hora. ISR com janela de revalidação de 1 hora era perfeito
  4. O time da universidade conhecia React — eles manteriam isto após a entrega

Se você está pesando opções similares, nossa página capacidades de desenvolvimento Next.js cobre os detalhes técnicos do que normalmente construímos.

Decisões de Arquitetura

Seleção do CMS Headless

Avaliamos cinco opções de CMS headless contra os requisitos da universidade: 80+ editores de conteúdo, relacionamentos de conteúdo complexos, permissões baseadas em função, e um modelo de preço por asento razoável.

Nós aterrissamos em Sanity para este projeto. Os fatores principais:

  • Consultas GROQ trataram os relacionamentos de taxonomia complexos entre programas, departamentos e faculdades muito melhor que GraphQL para este caso de uso
  • Colaboração em tempo real — múltiplos editores poderiam trabalhar simultaneamente sem conflitos
  • Componentes de entrada personalizados — construímos um mapeador de pré-requisitos de programa diretamente no studio
  • Preço — o plano Enterprise a ~$949/mês estava bem dentro do orçamento, e o custo por usuário era previsível

A modelagem de conteúdo levou cerca de duas semanas. Definimos 14 tipos de documento e 8 tipos de referência. O esquema de programa sozinho tinha 34 campos, incluindo dados estruturados para markup EducationalOrganization e Course schema.org.

Para mais sobre nossa abordagem à arquitetura de CMS, veja nossa página desenvolvimento de CMS headless.

Infraestrutura

Deployamos no Vercel para o frontend Next.js (o plano Enterprise, necessário para conformidade FERPA e requisitos SSO). As rotas autenticadas do portal de alunos usam renderização no servidor com gerenciamento de sessão através do CAS (Central Authentication Service) SSO existente da universidade.

O fluxo de dados se parece com isto:

[Sanity CMS] → [Next.js no Vercel] → [CDN de Borda]
                    ↕
           [API SIS da Universidade]
                    ↕
           [CAS SSO / LDAP]

Páginas de programa estáticas são pré-renderizadas no tempo de build e revalidadas a cada hora via ISR. O localizador de programas usa uma combinação de dados pré-carregados (carregados no cliente no tempo de build como um índice JSON) e filtragem em tempo real — nenhuma viagem de servidor necessária para operações de busca.

A Camada de API

O sistema de informações de alunos (Ellucian Banner, se você está curioso — é sempre Banner) expôs uma API SOAP. Sim, em 2024. Construímos uma camada de tradução usando rotas de API do Next.js que consumiam os endpoints SOAP e expunham endpoints REST limpos para nosso 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 camada de tradução foi uma das peças de maior valor do projeto. Ela desacoplou o frontend das peculiaridades de Banner e deu à universidade uma API limpa que eles poderiam usar para projetos futuros (um app mobile já estava sendo discutido).

Estudo de Caso: Migrando um Portal Universitário de Drupal para Next.js - arquitetura

O Localizador de Programas: Reconstruindo o Recurso Principal

O localizador de programas era a página mais importante de todo o site. A análise mostrou que representava 34% de todo o tráfego de busca orgânica e era o #1 ponto de entrada para alunos prospectivos. Errar nisso não era uma opção.

A Abordagem Antiga (E Por Que Era Lenta)

A versão Drupal usava Views com filtros expostos. Cada mudança de filtro acionava uma viagem de servidor completa, consultava novamente o banco de dados e re-renderizava a página inteira. Com 200+ programas e 6 dimensões de taxonomia (nível de diploma, faculdade, departamento, formato de entrega, área de interesse e busca por palavra-chave), a consulta era cara.

A Nova Abordagem

Pré-construímos um índice de busca no tempo de build. Todos os 200+ programas foram serializados em um arquivo JSON de ~180KB (comprimido com gzip para ~22KB) que viaja com a página. A filtragem acontece inteiramente no lado do cliente usando um 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 busca de texto fuzzy e filtragem JavaScript simples para facetas. O resultado: resultados de busca aparecem em menos de 50ms. Sem spinners de carregamento. Sem chamadas de servidor. Usuários podem bater nos filtros tão rápido quanto quiserem.

Cada resultado de programa vincula a uma página de detalhe gerada estaticamente com markup schema.org completo, o que melhorou dramaticamente a aparência da universidade nos recursos de busca relacionados à educação do Google.

Migração do Portal de Alunos

O portal de alunos foi a parte mais complicada. Exigia autenticação, personalização e dados em tempo real de Banner. Não poderíamos gerar estaticamente nenhum deles.

Fluxo de Autenticação

A universidade usa CAS para single sign-on em todos os sistemas institucionais. Integramos CAS com Next.js usando um fluxo de autenticação personalizado:

  1. Usuário não autenticado acessa /portal → redirecionado para login CAS
  2. CAS redireciona de volta com um ticket de serviço
  3. Nossa rota de API valida o ticket contra o servidor CAS
  4. Criamos um JWT assinado armazenado em um cookie httpOnly
  5. Requisições subsequentes usam o JWT para gerenciamento de sessão

Usamos next-auth (agora Auth.js) com um provedor CAS personalizado que escrevemos do zero, já que nenhum provedor CAS mantido existia na época.

Recursos do Portal

O portal de alunos incluia:

  • Dashboard personalizado com datas de matrícula próximas, retenções e info de conselheiro
  • Resumo de auditoria de diploma puxado de Banner em tempo real
  • Quick links para o LMS (Canvas), email e sistemas de biblioteca
  • Recursos específicos do programa baseado no curso principal declarado do aluno

Todas as páginas do portal usam renderização no servidor. Armazenamos em cache respostas de API de Banner agressivamente (TTL de 30 segundos para a maioria dos endpoints, TTL de 5 minutos para auditorias de diploma) para evitar sobrecarregar seu sistema.

Estratégia de Migração de Conteúdo

Migrar 12.000 nós de conteúdo de Drupal para Sanity exigiu uma abordagem sistemática. Construímos um pipeline de migração personalizado:

# Pipeline de migração simplificado
1. Exportar nós Drupal → JSON via comandos Drush personalizados
2. Transformar JSON → formato de documento Sanity via scripts Node.js
3. Processar arquivos de mídia → carregar para CDN Sanity
4. Importar documentos → API de Migração Sanity
5. Validar → verificações automatizadas para referências quebradas

A migração de mídia foi a parte mais tediosa. O gerenciamento de arquivos de Drupal armazena arquivos com caminhos internos e referências de banco de dados. Escrevemos um script que:

  1. Baixou cada arquivo do diretório de arquivos Drupal
  2. Carregou para o pipeline de ativos Sanity
  3. Mapeou IDs de arquivo antigos Drupal para referências de ativos novos Sanity
  4. Atualizou todo conteúdo de rich text para apontar para as novas referências de ativos

Este script foi executado por cerca de 14 horas no dataset completo. Executamos três vezes durante o projeto: uma para teste inicial, uma no ponto médio para atualizar o staging, e uma para o cutover final.

Estratégia de Congelamento de Conteúdo

Implementamos um congelamento de conteúdo em duas fases:

  • Semanas 1-20: Editores de conteúdo trabalham em Drupal como normal. Migramos snapshots para staging semanalmente.
  • Semanas 21-23: Entrada dupla. Novo conteúdo vai para ambos Drupal e Sanity. Editores treinados em novo CMS.
  • Semana 24: Cutover completo. Drupal fica somente leitura, depois offline.

O período de entrada dupla foi doloroso mas necessário. Tínhamos 80+ editores, e eles precisavam construir memória muscular com Sanity antes de ser sua única opção.

Cronograma de 6 Meses

Mês Fase Entregas Principais
Mês 1 Descoberta & Arquitetura Alinhamento de stakeholders, seleção de CMS, configuração de infraestrutura, modelagem de conteúdo
Mês 2 Desenvolvimento Principal Sistema de design, templates de página, páginas de detalhe de programa, navegação
Mês 3 Localizador de Programas & Busca Índice de busca, UI de filtragem, pipeline de dados de programa, markup SEO
Mês 4 Portal de Alunos Integração CAS, camada de API Banner, dashboard, exibição de auditoria de diploma
Mês 5 Migração de Conteúdo & Treinamento Scripts de migração, treinamento de editor (6 sessões), QA de staging
Mês 6 QA, Desempenho, Launch Teste de carga, auditoria de acessibilidade, congelamento de conteúdo, cutover de DNS

Nosso time era 4 desenvolvedores, 1 designer, e 1 gerente de projeto. A universidade forneceu um product owner dedicado mais um liaison de TI para o trabalho de integração Banner/CAS.

Nos deparamos com dois problemas principais:

  1. Mês 3: A API SOAP de Banner tinha um limite de taxa não documentado de 100 requisições/minuto. Nosso localizador de programas foi projetado para fazer batch-fetch de todos os dados de programa durante o build. Tivemos que implementar um sistema de fila e espalhar o build em múltiplos batches.

  2. Mês 5: A auditoria de acessibilidade encontrou 34 violações WCAG 2.1 AA. A maioria foi herdada do design (contraste de cor insuficiente em botões secundários, indicadores de foco faltantes nos filtros do localizador de programas). Gastamos 8 dias não planejados em remediação.

Resultados de Desempenho

Aqui está como os números ficaram após o launch:

Métrica Drupal 7 (Antes) Next.js (Depois) Melhoria
LCP 6.2s 1.1s 82% mais rápido
FID / INP 380ms 45ms 88% mais rápido
CLS 0.31 0.02 94% melhor
TTFB 2.8s 0.12s 96% mais rápido
Carregamento do Localizador de Programas 8.4s 0.8s 90% mais rápido
Pontuação Lighthouse 34 97 +63 pontos
Tempo de build (completo) N/A 4m 12s
Custo de hospedagem mensal ~$2.400 ~$1.100 54% menor

Mas os números que mais importavam para a universidade eram estes:

  • Uso do localizador de programas aumentou 156% no primeiro semestre após o launch
  • Taxa de rejeição mobile caiu de 67% para 31%
  • Tráfego de busca orgânica para páginas de programa aumentou 43% dentro de 4 meses (markup schema.org + melhorias de Core Web Vitals)
  • Tickets de suporte relacionados ao portal caíram 62% — em grande parte porque as páginas realmente carregavam de forma confiável
  • Zero downtime durante matrícula de outono — a primeira vez em três anos

Lições Aprendidas

1. Comece a Integração CAS/SSO Cedo

Agendamos a integração CAS para o Mês 4. Deveríamos ter começado uma prova de conceito no Mês 1. Times de TI universitários se movem deliberadamente (leia: lentamente) através de revisões de segurança. Obter a arquitetura SSO aprovada levou três semanas de discussões com seu escritório de segurança.

2. Modelagem de Conteúdo É Arquitetura

Gastamos duas semanas completas na modelagem de conteúdo antes de escrever qualquer código frontend. Isso pareceu lento na época. Foi o investimento único melhor que fizemos. Quando você tem 200+ programas com relacionamentos complexos entre departamentos, faculdades, níveis de diploma, concentrações e formatos de entrega, acertar o esquema antecipadamente economiza centenas de horas de refatoração.

3. Treine Editores Cedo, Não Apenas Antes do Launch

Originalmente planejamos treinamento de editor para o Mês 5. Movemos para o Mês 4 após feedback do product owner. Isso deu aos editores seis semanas para ficar confortável com Sanity em vez de duas. A qualidade do conteúdo inserido durante o período de entrada dupla foi dramaticamente melhor por causa disso.

4. Banner É Banner

Se você está trabalhando com Ellucian Banner (e se você está em educação superior, provavelmente está), orce tempo extra para a integração de API. A documentação é escassa, os endpoints SOAP são inconsistentes, e cada instituição customizou sua instância de Banner diferentemente. Nossa camada de tradução foi essencial.

5. Orce por Acessibilidade Desde o Dia Um

Nossas 34 violações WCAG no Mês 5 foram quase inteiramente evitáveis. Agora executamos verificações axe-core em nosso pipeline de CI em cada pull request. Se você está construindo para uma universidade pública, conformidade WCAG 2.1 AA não é opcional — é um requisito legal sob Seção 508.

Se você está enfrentando um desafio de migração similar, estamos felizes em discutir os detalhes. Você pode entrar em contato conosco diretamente ou verificar nossa página de preços para como normalmente scopeamos esses projetos.

FAQ

Quanto tempo leva para migrar um website universitário de Drupal para Next.js?

Para um site desta escala — 12.000 nós de conteúdo, 200+ programas, portal de alunos autenticado — seis meses é realista com um time dedicado de 4-6 pessoas. Sites institucionais menores (menos de 2.000 páginas, sem portal) frequentemente podem ser feitos em 3-4 meses. O cronograma é impulsionado menos pela construção de frontend e mais por migração de conteúdo, alinhamento de stakeholders, e integração com sistemas institucionais como Banner ou PeopleSoft.

Qual CMS headless é melhor para websites de educação superior?

Depende do tamanho do seu time editorial e conforto técnico. Escolhemos Sanity para este projeto por causa de sua colaboração em tempo real, modelagem de conteúdo flexível, e linguagem de consulta GROQ. Contentful e Storyblok também são opções fortes. Para universidades com times de conteúdo muito grandes (100+ editores), o modelo de workflow e permissões de Contentful pode ser vantajoso. Para times menores que querem mais customização, Sanity tende a vencer.

Next.js pode lidar com portais de alunos autenticados?

Absolutamente. Next.js suporta renderização no servidor para páginas autenticadas, e os server components do App Router tornam direto buscar dados específicos do usuário sem expô-los ao bundle do cliente. Integramos com CAS (Central Authentication Service) usando Auth.js com um provedor personalizado. O portal tratou 40.000 alunos sem problemas de desempenho.

Quanto custa uma migração de Drupal para Next.js para uma universidade?

Um projeto dessa escala — localizador de programas, portal de alunos, 200+ programas, migração de conteúdo completa, setup de CMS, e treinamento — normalmente varia de $250.000 a $450.000 dependendo da complexidade. Entretanto, as economias a longo prazo são significativas. Esta universidade reduziu seus custos anuais de manutenção de aproximadamente $180K para $95K, significando que o projeto se paga em 3-4 anos mesmo no topo superior da faixa de orçamento.

O que acontece com SEO durante uma migração CMS em larga escala?

Esta é uma preocupação legítima. Implementamos um mapa de redirecionamento abrangente (mais de 2.400 redirecionamentos 301), preservamos todas as estruturas de URL existentes onde possível, e adicionamos dados estruturados schema.org que o site Drupal não tinha. O tráfego orgânico caiu cerca de 8% nas primeiras duas semanas pós-launch (normal para qualquer migração principal), depois se recuperou e excedeu o baseline em 43% dentro de quatro meses.

Drupal 10 é uma escolha melhor que ir headless para universidades?

Pode ser, dependendo da situação. Se seu time tem forte expertise Drupal, seus módulos personalizados têm compatibilidade Drupal 10, e você não precisa das características de desempenho de um site estático/híbrido, Drupal 10 é um caminho perfeitamente válido. No nosso caso, a universidade tinha perdido sua expertise Drupal, tinha 23 módulos incompatíveis, e precisava de melhorias dramáticas de desempenho. A abordagem headless era claramente o melhor ajuste.

Como você lida com migração de conteúdo de Drupal para um CMS headless?

Usamos scripts Node.js personalizados que exportam conteúdo Drupal via comandos Drush, transformam os dados para corresponder ao novo esquema de CMS, lidam com migração de arquivo de mídia, e importam tudo via API de migração do CMS. O processo normalmente é executado 3 vezes: uma para teste inicial, uma para atualizar staging, e uma para cutover final. Conteúdo de rich text com mídia incorporada é a parte mais difícil — você precisa remapear cada referência de arquivo interna.

Você pode executar Drupal e Next.js simultaneamente durante a migração?

Sim, e recomendamos. Durante nossa migração, Drupal continuou servindo o site de produção enquanto construíamos e testávamos a versão Next.js em um domínio de staging. Usamos um período de entrada dupla de três semanas onde conteúdo ia para ambos os sistemas. O cutover final foi uma mudança de DNS que levou cerca de 15 minutos, com Drupal mantido em modo somente leitura por 30 dias como fallback.