40,000人の学生、6ヶ月、ダウンタイムゼロ: Drupal → Next.js移行
プログラム検索が8.2秒でタイムアウト。また。登録週で学生ポータルは悲鳴を上げています。40,000人の学部生が同じ壊れた検索を更新し続け、サポートキューはチケットで満杯になり、入学VP は転換率がリアルタイムで低下するのを見ています。Drupal 7のセキュリティパッチは6ヶ月で終了します。200以上のプログラムページ、チームがほとんど理解していないレガシーCMS、そして譲れない要件があります:ダウンタイムゼロ。2024年初頭、大規模州立大学はまさにこの問題を私たちに与えました。Drupal 7が暗くなる前にNext.jsへの完全な移行が必要でした。そしてプログラム検索がどんどん学生を失うことをやめる必要がありました。26週間で彼らのポータル全体を再構築し、検索応答時間を340msに短縮し、サイトをオフラインにすることなく展開した方法をここに紹介します。
これは、全体をNext.jsに移行し、ヘッドレスCMSバックエンドを使用し、ページロード時間を73%削減し、スケジュール通りに発送したストーリーです。私たちが行ったアーキテクチャの決定(そして間違うところだった決定)、実際の移行プロセス、パフォーマンスベンチマーク、および大規模CMS移行に適用される教訓を共有します。
目次
- スタート地点:何と格闘していたのか
- Next.jsを選んだ理由(そしてDrupal 10を選ばなかった理由)
- アーキテクチャの決定
- プログラムファインダー:コア機能の再構築
- 学生ポータルの移行
- コンテンツ移行戦略
- 6ヶ月のタイムライン
- パフォーマンス結果
- 学んだ教訓
- FAQ

スタート地点:何と格闘していたのか
状況を説明しましょう。大学のデジタルプレゼンスはDrupal 7で構築されており、2014年頃に最初に起動されました。過去10年間、蓄積されてきた:
- 約12,000のコンテンツノードプログラム、コース、教員プロフィール、ニュース記事、イベントにまたがっています
- 200以上のアカデミックプログラムページ複雑な分類法関係を持つそれぞれのもの(学位レベル、学部、カレッジ、配信形式、認定ステータス)
- カスタムプログラムファインダー Drupal Viewsベースの検索として構築されているフィルタが公開されている — 機能的だが遅い
- 学生ポータルアドバイスツール、学位監査、登録リンク、および個人化されたダッシュボードに認証済みアクセス
- 47のカスタムDrupalモジュールそのうち19は管理されていません
- 3つの異なるテーマレイヤー連続的な再設計から積み重ねられた
サイトは機関的なロードバランサーの後ろの2つの古い仮想マシンでホストされていました。ピーク登録期間(8月と1月)中、プログラムファインダーは定期的にタイムアウトしました。マーケティングチームはプログラムのPDFリストをバックアップとして投稿することに頼っていました。それがすべてを物語っています。
コアウェブバイタルは荒かった:
| メトリック | Drupal 7(前) | ターゲット |
|---|---|---|
| LCP | 6.2秒 | < 2.5秒 |
| FID | 380ms | < 100ms |
| CLS | 0.31 | < 0.1 |
| TTFB | 2.8秒 | < 0.8秒 |
| プログラムファインダー読込 | 8.4秒 | < 1.5秒 |
ステークホルダーの環境
大学ウェブプロジェクトは、ステークホルダーの数が原因で独自に難しいです。私たちは以下と協力していました:
- 中央IT — SSO統合、セキュリティコンプライアンス、およびホスティングを担当しています
- マーケティング&コミュニケーション — ブランド、コンテンツ戦略、分析を所有しています
- レジストラーのオフィス — プログラムデータと学生情報システム(SIS)を所有しています
- 個別の大学と部門 — それぞれに独自のコンテンツエディター(80人以上がCMSアクセス権を持つ)
- 学生会 — モバイルファーストデザインを積極的に提唱しています(当然のことながら)
これらすべてのグループから合意を得るのに、プロジェクトの最初の3週間がかかりました。共有された優先順位と譲れないものを確立するためにデザインスプリントを実行しました。
Next.jsを選んだ理由(そしてDrupal 10を選ばなかった理由)
明らかな質問:Drupal 10にアップグレードしないのはなぜですか。大学のITチームは実際にはその経路を6ヶ月前に私たちに連絡する前に開始していました。47個のカスタムモジュールのうち23個がDrupal 10の同等物を持たず、完全に書き直す必要があることを発見した後、彼らはそれを放棄しました。
実際の計算は次のようになりました:
| 因子 | Drupal 10移行 | Next.js再構築 |
|---|---|---|
| 推定タイムライン | 8-10ヶ月 | 6ヶ月 |
| カスタムモジュール書き直し | 23モジュール | N/A(API/コンポーネントとして再構築) |
| コンテンツエディター再トレーニング | 中程度(新しい管理UI) | 中程度(新しいCMS) |
| パフォーマンス天井 | 中程度の改善 | 劇的な改善 |
| ホスティングの柔軟性 | 従来のLAMP/類似 | エッジ展開、CDN優先 |
| 開発者採用プール | 縮小中(Drupal専門家) | 拡大中(React/Next.js) |
| 長期メンテナンスコスト | 年間約180,000ドル | 年間約95,000ドル |
メンテナンスコストの違いが行政の決定者でした。機関的な経験を持つDrupal開発者はより見つけるのが難しくなり、保持するのがより高価になっていました。大学自身のITチームは、シニアDrupal開発者が退職した後、3人のReact開発者とゼロのDrupal専門家を持っていました。
私たちは具体的にNext.jsを選びました(Gatsby、Remix、またはAstroではなく)いくつかの理由から:
- ハイブリッドレンダリング — プログラムページを静的に生成できますが、学生ポータルには認証付きサーバー側のレンダリングが必要です
- APIルート — 個別のバックエンドサービスなしでSIS統合用のミドルウェアを構築できます
- 増分静的再生成(ISR) — プログラムデータは毎日変わります、1時間ごとではありません。1時間の再検証ウィンドウを持つISRは完璧です
- 大学のチームはReactを知っていました — 彼らはハンドオフの後これを維持するでしょう
同様のオプションを検討している場合、私たちのNext.js開発機能ページは通常構築するものの技術仕様をカバーしています。
アーキテクチャの決定
ヘッドレスCMS選択
大学の要件に対して5つのヘッドレスCMSオプションを評価しました:80人以上のコンテンツエディター、複雑なコンテンツ関係、ロールベースの権限、および合理的なシート当たりの価格設定モデル。
私たちはこのプロジェクトのためにSanityに着陸しました。重要な要因:
- GROQクエリはプログラム、部門、大学間の複雑な分類法関係をGraphQLよりもはるかにうまく処理しました
- リアルタイム協力 — 複数のエディターは競合なしに同時に作業できます
- カスタム入力コンポーネント — スタジオに直接プログラム前提条件マッパーを構築しました
- 価格設定 — Enterpriseプランは月額約949ドルで予算内でした、シート当たりのコストは予測可能でした
コンテンツモデリングには約2週間かかりました。14のドキュメントタイプと8つの参照タイプを定義しました。プログラムスキーマ単体は34のフィールドを持っていました、schema.org EducationalOrganization および Course マークアップを含む。
CMSアーキテクチャへのアプローチに関する詳細は、私たちのヘッドレスCMS開発ページを参照してください。
インフラストラクチャ
Next.jsフロントエンドをVercelに展開しました(FERPA準拠とSSO要件に必要なEnterpriseプラン)。学生ポータルの認証されたルートは、大学の既存のCAS(中央認証サービス) SSOを通じたセッション管理を使用してサーバー側のレンダリングを使用します。
データフローは次のようになります:
[Sanity CMS] → [Vercel上のNext.js] → [CDNエッジ]
↕
[大学SIS API]
↕
[CAS SSO / LDAP]
静的プログラムページはビルド時に事前にレンダリングされ、ISRを介して毎時間再検証されます。プログラムファインダーはビルド時に事前に取得されたデータ(JSONインデックスとしてクライアントに読み込まれる)とリアルタイムフィルタリングの組み合わせを使用します — 検索操作に対してサーバーのラウンドトリップは不要です。
APIレイヤー
学生情報システム(Ellucian Banner、もし興味がある場合 — それはいつもBannerです)はSOAPAPIを公開しました。そう、2024年に。Next.js APIルートを使用してSOAPエンドポイントを消費し、フロントエンドに対してクリーンなRESTエンドポイントを公開する翻訳レイヤーを構築しました:
// /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',
},
});
}
この翻訳レイヤーはプロジェクトの最も高い値の部分の1つでした。フロントエンドをBannerの怪癖から分離し、大学に将来のプロジェクト(モバイルアプリについて既に議論されていた)のために使用できるクリーンなAPIを与えました。

プログラムファインダー:コア機能の再構築
プログラムファインダーはサイト全体で最も重要なページでした。分析によると、すべてのオーガニック検索トラフィックの34%を占め、見込み学生向けの#1エントリーポイントでした。これを間違えることはオプションではありませんでした。
古いアプローチ(そしてそれが遅かった理由)
Drupalバージョンは、フィルタが公開されているViewsを使用しました。フィルタの変更はすべて、完全なサーバーラウンドトリップをトリガーし、データベースを再照会し、ページ全体を再レンダリングしました。200以上のプログラムと6つの分類法ディメンション(学位レベル、大学、部門、配信形式、興味の領域、キーワード検索)で、クエリは高価でした。
新しいアプローチ
ビルド時に検索インデックスを事前構築しました。200以上のプログラムすべてが約180KBのJSONファイル(約22KBにgzip)にシリアル化されており、ページに付属しています。フィルタリングはカスタムフックを使用して完全にクライアント側で行われます:
// 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 };
}
Fuse.jsをあいまいなテキスト検索に使用し、ファセットに対してプレーンなJavaScriptフィルタリングを使用しました。結果:検索結果が50ms未満で表示されます。ローディングスピナーはありません。サーバー呼び出しはありません。ユーザーはできるだけ速くフィルタをハンマーできます。
各プログラムの結果は、完全なschema.orgマークアップを備えた静的に生成された詳細ページにリンクしており、大学の外観をGoogleの教育関連の検索機能で大幅に改善しました。
学生ポータルの移行
学生ポータルは最も難しい部分でした。認証、個人化、およびBannerからのリアルタイムデータが必要でした。何も静的に生成することはできませんでした。
認証フロー
大学は、すべての機関システム全体にシングルサインオンのためにCASを使用しています。カスタム認証フローを使用してCASをNext.jsと統合しました:
- 認証されていないユーザーが
/portalに当たる → CAS ログインにリダイレクト - CASがサービスチケットでリダイレクト返す
- 私たちのAPIルートはCASサーバーに対してチケットを検証します
- 我々はhttpOnlyクッキーに保存された署名付きJWTを作成します
- 以降のリクエストはセッション管理にJWTを使用します
当時Casプロバイダーが存在していないため、next-auth(現在はAuth.js)をカスタムCASプロバイダーと一緒に使用しました。
ポータル機能
学生ポータルに含まれていた:
- 個人化されたダッシュボード今後の登録日、hold、アドバイザー情報を使用
- 学位監査概要Bannerからリアルタイムで引き出されました
- クイックリンク LMS(Canvas)、メール、およびライブラリシステムへ
- プログラム固有のリソース学生の宣言された主体に基づいて
すべてのポータルページはサーバー側レンダリングを使用します。ほとんどのエンドポイント(30秒TTL、学位監査用5分TTL)に対してBanner APIレスポンスをアグレッシブにキャッシュして、それらのシステムを圧倒しないようにします。
コンテンツ移行戦略
Drupalから12,000個のコンテンツノードをSanityに移行するには、体系的なアプローチが必要でした。カスタム移行パイプラインを構築しました:
# 簡略化された移行パイプライン
1. DrupalノードをエクスポートJSONをカスタムDrushコマンド経由で
2. JSONを変換Sanityドキュメント形式へNode.jsスクリプト経由で
3. メディアファイル処理Sanity CDNにアップロード
4. ドキュメントをインポートSanity Migration API経由で
5. 検証壊れた参照に対する自動チェック
メディア移行は最も退屈な部分でした。Drupalのファイル管理はファイルを内部パスとデータベース参照で保存しています。以下を実行するスクリプトを作成しました:
- Drupalファイルディレクトリからすべてのファイルをダウンロード
- Sanityのアセットパイプラインにアップロード
- 古いDrupalファイルIDを新しいSanityアセット参照にマッピング
- リッチテキストコンテンツを更新して新しいアセット参照を指す
このスクリプトはフルデータセットで約14時間実行されました。プロジェクト中に3回実行しました:初期テスト用、中点での更新用、最後のカットオーバー用。
コンテンツフリーズ戦略
2段階のコンテンツフリーズを実装しました:
- 週1-20:コンテンツエディターは通常のようにDrupalで機能します。毎週スナップショットをステージングに移行します。
- 週21-23:デュアルエントリ。新しいコンテンツはDrupalとSanityの両方に入ります。新しいCMSで訓練されたエディター。
- 週24:完全なカットオーバー。Drupalは読み取り専用になり、その後オフラインになります。
デュアルエントリ期間は苦しかった。80人以上のエディターがいて、唯一のオプションになる前にSanityで筋肉記憶を構築する必要がありました。
6ヶ月のタイムライン
| 月 | フェーズ | 主要な成果物 |
|---|---|---|
| 月1 | 発見とアーキテクチャ | ステークホルダー合意、CMS選択、インフラセットアップ、コンテンツモデリング |
| 月2 | コア開発 | デザインシステム、ページテンプレート、プログラム詳細ページ、ナビゲーション |
| 月3 | プログラムファインダーと検索 | 検索インデックス、UI をフィルタリング、プログラムデータパイプライン、SEOマークアップ |
| 月4 | 学生ポータル | CAS統合、Banner APIレイヤー、ダッシュボード、学位監査表示 |
| 月5 | コンテンツ移行と訓練 | 移行スクリプト、エディター訓練(6セッション)、ステージングQA |
| 月6 | QA、パフォーマンス、起動 | ロード テスト、アクセシビリティ監査、コンテンツフリーズ、DNSカットオーバー |
私たちのチームは4人の開発者、1人のデザイナー、および1人のプロジェクトマネージャーでした。大学はプロダクトオーナー専用と、Banner/CAS統合作業用のIT連絡先を提供しました。
2つの主要な落とし穴がありました:
月3:BannerのSOAPAPIには1分あたり100リクエストのドキュメント化されていないレート制限がありました。プログラムファインダーはビルド時にすべてのプログラムデータをバッチフェッチするように設計されていました。キューイングシステムを実装し、複数のバッチにビルドを広げる必要がありました。
月5:アクセシビリティ監査はWCAG 2.1 AA違反の34を見つけました。ほとんどはデザインから継承されました(セカンダリボタンの不十分な色のコントラスト、プログラムファインダーフィルターのフォーカス指標の欠落)。予期しない8日間の修復に費やしました。
パフォーマンス結果
起動後の数字は次のようになりました:
| メトリック | Drupal 7(前) | Next.js(後) | 改善 |
|---|---|---|---|
| LCP | 6.2秒 | 1.1秒 | 82%高速 |
| FID / INP | 380ms | 45ms | 88%高速 |
| CLS | 0.31 | 0.02 | 94%改善 |
| TTFB | 2.8秒 | 0.12秒 | 96%高速 |
| プログラムファインダー読込 | 8.4秒 | 0.8秒 | 90%高速 |
| ライトハウススコア | 34 | 97 | +63ポイント |
| ビルド時間(フル) | N/A | 4m 12s | — |
| 月次ホスティングコスト | 約2,400ドル | 約1,100ドル | 54%低い |
しかし、大学にとって最も重要な数字はこれらでした:
- プログラムファインダーの使用は156%増加起動後の最初の学期に
- モバイルバウンスレート67%から31%に低下
- プログラムページへのオーガニック検索トラフィックが43%増加起動後4ヶ月以内(schema.orgマークアップ+コアウェブバイタル改善)
- ポータルに関連するサポートチケット62%削減 — 主にページが実際に確実に読み込まれたため
- 秋の登録中のダウンタイムゼロ — 3年間で初めて
学んだ教訓
1. CAS/SSO統合を早期に開始
CAS統合を月4でスケジュールしました。月1でProof of Conceptを開始する必要がありました。大学IT チームは意図的に(読む:ゆっくり)セキュリティレビューを進みます。SSOアーキテクチャを承認させるのに、セキュリティオフィスとの3週間の往復がかかりました。
2. コンテンツモデリングはアーキテクチャ
フロントエンドコードを作成する前に、コンテンツモデリングに2週間を費やしました。当時は遅いように感じました。それは私たちが作った最高の投資でした。200以上のプログラムを持つ場合、学部、大学、学位レベル、集中、および配信形式間の複雑な関係により、スキーマを事前に正しく取得すると、数百時間のリファクタリングを節約できます。
3. エディターを早期に訓練、起動前だけではない
最初は月5でエディター訓練を計画していました。製品所有者からのフィードバックの後、それを月4に移しました。これにより、エディターはデュアルエントリ期間中の2週間ではなく、6週間のうちにSanityに慣れさせます。デュアルエントリ期間中に入力されたコンテンツの品質は、この理由により劇的に改善されました。
4. BannerはBanner
Ellucian Banner(高等教育にいるなら、あなたはおそらくそうです)で作業している場合、APIIntegrationに余分な時間を予算してください。ドキュメントはまばらで、SOAPエンドポイントは一貫性がなく、すべての機関はそれらのBannerインスタンスをカスタマイズしています。私たちの翻訳レイヤーは不可欠でした。
5. 日から始まるアクセシビリティの予算
月5のアクセシビリティ監査でWCAG違反の34個はほぼすべて予防可能でした。axe-coreチェックをci パイプラインで実行して、プルリクエストごとに実行しています。公立大学向けにビルディングしている場合、WCAG 2.1 AA準拠はオプション ではありません — セクション508の下での法的要件です。
同様の移行チャレンジに直面している場合、具体的について話し合うのは幸せです。直接私たちに連絡するか、プロジェクトをどのようにスコープするかについて、価格設定ページをチェックしてください。
FAQ
DrupalからNext.jsへ大学ウェブサイトを移行するのにどのくらい時間がかかりますか?
このスケールのサイトの場合 — 12,000のコンテンツノード、200以上のプログラム、認証済み学生ポータル — 6ヶ月は4-6人の専任チームでリアルです。より小さな機関サイト(2,000ページ未満、ポータルなし)は多くの場合3-4ヶ月で実行できます。タイムラインはフロントエンドビルドよりもコンテンツ移行、ステークホルダー合意、Bannerまたは PeopleSoftのような機関システムとの統合によって駆動されます。
高等教育ウェブサイトに最適なヘッドレスCMSは何ですか?
編集チームのサイズと技術的快適さによって異なります。プロジェクトの場合、GROQクエリ言語、リアルタイム協力、および柔軟なコンテンツモデリングのためにSanityを選択しました。ContentfulとStoryblokも強いオプションです。非常に大きなコンテンツチームを持つ大学(100人以上のエディター)の場合、Contentfulのワークフローと権限モデルは有利になる可能性があります。より小さなチームがより多くのカスタマイズを望む場合、Sanityは勝つ傾向があります。
Next.jsは認証された学生ポータルを処理できますか?
絶対に。Next.jsはサーバー側のレンダリングを使用した認証ページをサポートしており、App Routerのサーバーコンポーネントはクライアントバンドルに公開することなく、ユーザー固有のデータを取得するのは簡単にしてくれます。カスタムプロバイダーを使用してCAS(Central Authentication Service)をAuth.jsと統合しました。ポータルはパフォーマンスの問題なく、40,000人の学生を処理しました。
大学向けDrupalからNext.jsへの移行のコストはいくらですか?
このスコープのプロジェクト — プログラムファインダー、学生ポータル、200以上のプログラム、完全なコンテンツ移行、CMS セットアップ、および訓練 — 通常は複雑さに応じて250,000ドルから450,000ドルの範囲です。ただし、長期的な節約は重大です。この大学は年間メンテナンスコストを約180,000ドルから95,000ドルに削減しました。つまり、プロジェクトは予算範囲の高端でも3-4年以内に支払う自分自身です。
大規模CMS移行中にSEOに何が起こりますか?
これは正当な懸念です。包括的なリダイレクトマップ(2,400以上の301リダイレクト)を実装し、可能な限り既存のURL構造を保存し、Drupalサイトが欠けていたschema.org構造化データを追加しました。オーガニックトラフィックは起動後の最初の2週間で約8%下落しました(大規模移行の場合、正常です)。その後、4ヶ月以内にベースラインを43%超えました。
大学向けDrupal 10は Next.jsへのヘッドレスに行くよりも優れた選択ですか?
状況によっては、はい。強いDrupal専門知識があり、カスタムモジュールがDrupal 10互換性を持ち、静的/ハイブリッドサイトのパフォーマンス特性が必要ない場合、Drupal 10は完全に有効なパスです。私たちの場合、大学はDrupal専門知識を失い、23の互換性のないモジュールを持ち、劇的なパフォーマンス改善が必要でした。ヘッドレスアプローチは明らかに良い適切でした。
DrupalからヘッドレスCMSへのコンテンツ移行をどのように処理しますか?
カスタムNode.jsスクリプトを使用します。これはDrushコマンド経由でDrupalコンテンツをエクスポート、データを新しいCMSスキーマに一致するように変換、メディアファイル移行を処理、CMSの移行API経由ですべてをインポートします。プロセスは通常3回実行されます:初期テスト用、ステージング更新用、最終カットオーバー用。埋め込みメディア付きのリッチテキストコンテンツが最も難しい部分です — すべての内部ファイル参照をリマップする必要があります。
移行中にDrupalとNext.jsを同時に実行できますか?
はい、推奨しています。移行中、Drupalは本番サイトを提供し続け、一方でNext.jsバージョンをステージングドメイン上に構築してテストしました。コンテンツが両方のシステムに入った3週間のデュアルエントリ期間を使用しました。最終的なカットオーバーはDrupal をフォールバック用の読み取り専用モードに保つ30日間で、約15分かかったDNSスイッチでした。