個案研究:將大學入口網站從 Drupal 遷移至 Next.js
2024年大學門戶從Drupal遷移到Next.js案例研究
在2024年初,一所大型州立大學找上我們,他們遇到了高等教育中變得越來越常見的問題:他們的Drupal 7安裝即將終止支持,學生門戶在招生期間不堪負荷,而他們網站上最重要的轉化工具——課程查找器——需要8秒以上才能返回搜索結果。他們有40,000名活躍學生、200多個學術項目,還有六個月的窗口期讓Drupal 7安全支持有效期結束。毫無疑問,壓力很大。
這是我們如何將整個系統遷移到Next.js並使用headless CMS後端、將頁面加載時間減少73%、並按計劃交付的故事。我將分享我們做出的架構決策(以及我們幾乎出錯的決策)、實際遷移過程、性能基準測試以及適用於任何大規模CMS遷移的經驗教訓。
目錄

起點:我們使用的系統
讓我描繪一下這個狀況。該大學的數字化呈現基於Drupal 7,最初在2014年左右推出。在過去十年裡,它已經累積了:
- 約12,000個內容節點,涵蓋項目、課程、教職員檔案、新聞文章和活動
- 200多個學術項目頁面,每個都具有複雜的分類關係(學位級別、部門、學院、交付方式、認證狀況)
- 自訂課程查找器,建立為基於Drupal Views的搜索,具有公開過濾器——功能正常但速度慢
- 學生門戶,具有對建議工具、學位審核、註冊鏈接和個性化儀表板的認證存取
- 47個自訂Drupal模塊,其中19個不再維護
- 3個不同的主題層,從連續的設計重做堆疊在彼此之上
該網站託管在機構負載平衡器後面的兩台老化虛擬機上。在高峰招生期間(8月和1月),課程查找器會定期超時。市場營銷團隊已經開始發佈課程的PDF列表作為備份。這說明了一切。
核心網頁指標很糟糕:
| 指標 | Drupal 7(之前) | 目標 |
|---|---|---|
| LCP | 6.2s | < 2.5s |
| FID | 380ms | < 100ms |
| CLS | 0.31 | < 0.1 |
| TTFB | 2.8s | < 0.8s |
| 課程查找器加載 | 8.4s | < 1.5s |
利益相關者格局
大學網絡項目因為利益相關者數量眾多而面臨獨特的挑戰。我們與以下部門合作:
- 中央IT部門——負責SSO整合、安全合規和託管
- 營銷與通訊部門——擁有品牌、內容策略和分析
- 註冊辦公室——擁有課程數據和學生信息系統(SIS)
- 各學院與部門——每個都有自己的內容編輯者(超過80人有CMS存取權限)
- 學生會——強烈提倡行動優先設計(理所當然)
從所有這些小組獲得協調花了項目的前三週時間。我們進行了一次設計衝刺來建立共享優先事項和非協商的要求。
為什麼選擇Next.js(以及為什麼不選擇Drupal 10)
顯而易見的問題:為什麼不直接升級到Drupal 10?該大學的IT團隊實際上在聯繫我們前六個月開始了這條路。他們在發現47個自訂模塊中有23個沒有Drupal 10的等效物且需要完全重寫後放棄了。
真實的計算看起來像這樣:
| 因素 | Drupal 10遷移 | Next.js重建 |
|---|---|---|
| 預計時間表 | 8-10個月 | 6個月 |
| 自訂模塊重寫 | 23個模塊 | N/A(重建為API/組件) |
| 內容編輯者再培訓 | 中等(新管理UI) | 中等(新CMS) |
| 性能上限 | 中等改進 | 劇烈改進 |
| 託管靈活性 | 傳統LAMP/類似 | 邊緣部署、CDN優先 |
| 開發者招聘池 | 萎縮(Drupal專家) | 增長(React/Next.js) |
| 長期維護成本 | 約$180K/年 | 約$95K/年 |
維護成本差異是行政部門的決定性因素。具有機構經驗的Drupal開發者越來越難找到,保留的成本也越來越高。該大學自己的IT團隊在他們的高級Drupal開發者退休後有三名React開發者和零名Drupal專家。
我們特別選擇Next.js(相對於Gatsby、Remix或Astro)有幾個原因:
- 混合渲染——課程頁面可以靜態生成,而學生門戶需要使用身份驗證的服務器端渲染
- API路由——我們可以為SIS整合構建中間件,無需單獨的後端服務
- 增量靜態再生成(ISR)——課程數據每週更改,不是每小時。帶有1小時重新驗證窗口的ISR是完美的
- 該大學的團隊知道React——他們將在交付後維護這個
如果您在衡量類似的選項,我們的Next.js開發能力頁面涵蓋了我們通常構建的技術細節。
架構決策
Headless CMS選擇
我們針對大學的要求評估了五個headless CMS選項:80多名內容編輯者、複雜的內容關係、基於角色的權限以及合理的每個座位定價模式。
我們在這個項目中選擇了Sanity。關鍵因素:
- GROQ查詢比GraphQL在這個使用情況下更好地處理課程、部門和學院之間的複雜分類關係
- 實時協作——多個編輯者可以同時工作而不會出現衝突
- 自訂輸入組件——我們直接在studio中構建了課程先決條件映射器
- 定價——企業計劃約$949/月在預算範圍內,每用戶成本是可預測的
內容建模花了大約兩週時間。我們定義了14個文檔類型和8個參考類型。課程schema本身有34個字段,包括schema.org EducationalOrganization和Course標記的結構化數據。
有關我們對CMS架構的方法,請參閱我們的headless CMS開發頁面。
基礎設施
我們在Vercel上部署了Next.js前端(企業計劃,FERPA合規性和SSO要求所需)。學生門戶的認證路由使用服務器端渲染,通過大學現有的CAS(中央身份驗證服務) SSO進行會話管理。
數據流看起來像這樣:
[Sanity CMS] → [Vercel上的Next.js] → [CDN邊緣]
↕
[大學SIS API]
↕
[CAS SSO / LDAP]
靜態課程頁面在構建時預先呈現,並通過ISR每小時重新驗證一次。課程查找器使用預取數據(在構建時作為JSON索引加載到客戶端)和實時過濾的組合——搜索操作不需要服務器往返。
API層
學生信息系統(Ellucian Banner,如果你好奇的話——永遠是Banner)公開了SOAP API。是的,在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',
},
});
}
這個轉換層是項目中最高價值的部分之一。它將前端與Banner的怪癖解耦,並給了大學一個乾淨的API,他們可以用於未來的項目(已經在討論移動應用)。

課程查找器:重建核心功能
課程查找器是整個網站上最重要的頁面。分析顯示它佔所有有機搜索流量的34%,是準學生的第一入口點。出錯不是一個選項。
舊方法(以及它為什麼很慢)
Drupal版本使用帶有公開過濾器的Views。每個過濾器更改都會觸發完整的服務器往返、重新查詢數據庫並重新呈現整個頁面。有200多個課程和6個分類維度(學位級別、學院、部門、交付方式、興趣領域和關鍵字搜索),查詢很昂貴。
新方法
我們在構建時預先構建了搜索索引。所有200多個課程被序列化為約180KB的JSON文件(gzip到約22KB),隨頁面一起提供。過濾完全在客戶端使用自訂hook進行:
// 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 cookie中的簽名JWT
- 後續請求使用JWT進行會話管理
我們使用next-auth(現在的Auth.js)與我們從頭開始編寫的自訂CAS提供者,因為當時不存在維護的CAS提供者。
門戶功能
學生門戶包括:
- 個性化儀表板,帶有即將進行的註冊日期、持有和顧問信息
- 學位審核摘要,從Banner實時提取
- 快速鏈接到LMS(Canvas)、電子郵件和圖書館系統
- 課程特定資源,基於學生的申報專業
所有門戶頁面都使用服務器端渲染。我們積極緩存Banner API響應(大多數端點的TTL為30秒,學位審核的TTL為5分鐘),以避免使他們的系統不堪重負。
內容遷移策略
將12,000個內容節點從Drupal遷移到Sanity需要系統性的方法。我們構建了自訂遷移管道:
# 簡化的遷移管道
1. 導出Drupal節點 → 通過自訂Drush命令使用JSON
2. 轉換JSON → Sanity文檔格式通過Node.js腳本
3. 處理媒體文件 → 上傳到Sanity CDN
4. 導入文檔 → Sanity遷移API
5. 驗證 → 自動檢查損壞的參考
媒體遷移是最繁瑣的部分。Drupal的文件管理存儲具有內部路徑和數據庫參考的文件。我們編寫了一個腳本來:
- 從Drupal文件目錄下載每個文件
- 將其上傳到Sanity的資產管道
- 將舊Drupal文件ID映射到新Sanity資產參考
- 更新所有富文本內容以指向新資產參考
該腳本在完整數據集上運行了大約14小時。我們在項目期間運行了三次:一次用於初始測試,一次用於刷新分段中點,一次用於最終轉換。
內容凍結策略
我們實施了兩階段內容凍結:
- 第1-20週:內容編輯者正常在Drupal中工作。我們每週向staging遷移快照。
- 第21-23週:雙重入境。新內容進入Drupal和Sanity。編輯者在新CMS上接受培訓。
- 第24週:完整轉換。Drupal變為只讀,然後離線。
雙重入境期很痛苦但必要。我們有80多名編輯,他們需要在Sanity成為唯一選擇之前用它建立肌肉記憶。
6個月時間表
| 月份 | 階段 | 關鍵交付成果 |
|---|---|---|
| 第1個月 | 發現與架構 | 利益相關者協調、CMS選擇、基礎設施設置、內容建模 |
| 第2個月 | 核心開發 | 設計系統、頁面模板、課程詳細頁面、導航 |
| 第3個月 | 課程查找器與搜索 | 搜索索引、過濾UI、課程數據管道、SEO標記 |
| 第4個月 | 學生門戶 | CAS整合、Banner API層、儀表板、學位審核顯示 |
| 第5個月 | 內容遷移與培訓 | 遷移腳本、編輯者培訓(6次會議)、staging QA |
| 第6個月 | QA、性能、啟動 | 負載測試、無障礙審計、內容凍結、DNS轉換 |
我們的團隊由4名開發者、1名設計師和1名項目經理組成。該大學提供了一名專職產品所有者加上一名IT聯絡官,負責Banner/CAS整合工作。
我們遇到了兩個主要障礙:
第3個月:Banner的SOAP API有一個未記錄的速率限制,每分鐘100個請求。我們的課程查找器被設計為在構建期間批量獲取所有課程數據。我們必須實施隊列系統並將構建分散到多個批次。
第5個月:無障礙審計發現了34個WCAG 2.1 AA違規。大多數都從設計中繼承(次要按鈕上的顏色對比度不足、課程查找器過濾器上的焦點指示符缺失)。我們在未計劃的8天內進行了補救。
性能結果
以下是啟動後的數字:
| 指標 | Drupal 7(之前) | Next.js(之後) | 改進 |
|---|---|---|---|
| LCP | 6.2s | 1.1s | 快82% |
| FID / INP | 380ms | 45ms | 快88% |
| CLS | 0.31 | 0.02 | 改進94% |
| TTFB | 2.8s | 0.12s | 快96% |
| 課程查找器加載 | 8.4s | 0.8s | 快90% |
| Lighthouse分數 | 34 | 97 | +63分 |
| 構建時間(完整) | N/A | 4m 12s | — |
| 月度託管成本 | ~$2,400 | ~$1,100 | 降低54% |
但對大學最重要的數字是:
- 課程查找器使用量在啟動後的第一個學期增加156%
- 行動反彈率從67%下降到31%
- 有機搜索流量到課程頁面在4個月內增加43%(schema.org標記+核心網頁指標改進)
- 與門戶相關的支持票減少62%——主要是因為頁面實際加載可靠
- 秋季招生期間零停機時間——三年來首次
經驗教訓
1. 儘早開始CAS/SSO整合
我們為第4個月安排了CAS整合。我們應該在第1個月開始概念驗證。大學IT團隊有計畫地(讀:緩慢)進行安全審查。讓SSO架構獲得批准花了三週的來回協商。
2. 內容建模是架構
我們在編寫任何前端代碼之前花了整整兩週時間進行內容建模。這在當時感覺很慢。這是我們所做的最好投資。當你有200多個課程,其中部門、學院、學位級別、濃度和交付方式之間具有複雜關係時,預先正確獲取schema可以節省數百小時的重構。
3. 儘早培訓編輯者,而不是在啟動前
我們最初計劃在第5個月進行編輯者培訓。在產品所有者的反饋後,我們將其移至第4個月。這給了編輯者六週時間來適應Sanity,而不是兩週。雙重入境期間輸入的內容質量因此大大提高。
4. Banner就是Banner
如果你正在使用Ellucian Banner(如果你在高等教育中,你可能是),為API整合預算額外時間。文檔稀疏,SOAP端點不一致,每個機構都自訂了他們的Banner實例。我們的轉換層是必不可少的。
5. 從第一天開始為無障礙編預算
我們在第5個月的34個WCAG違規幾乎完全是可預防的。我們現在在CI管道中在每個拉取請求上運行axe-core檢查。如果你為公開大學構建,WCAG 2.1 AA合規性不是可選的——這是第508節下的法律要求。
如果你面臨類似的遷移挑戰,我們很樂意討論細節。你可以直接與我們聯繫或查看我們的定價頁面以了解我們通常如何確定這些項目的範圍。
常見問題
將大學網站從Drupal遷移到Next.js需要多長時間? 對於這個規模的網站——12,000個內容節點、200多個課程、認證的學生門戶——六個月對於擁有4-6名開發者的專職團隊來說是現實的。較小的機構網站(少於2,000個頁面,沒有門戶)通常可以在3-4個月內完成。時間表由內容遷移、利益相關者協調和與Banner或PeopleSoft等機構系統的整合驅動,而不是前端構建。
哪個headless CMS最適合高等教育網站? 這取決於你的編輯團隊的規模和技術舒適度。我們為這個項目選擇了Sanity,因為它的實時協作、靈活的內容建模和GROQ查詢語言。Contentful和Storyblok也是強有力的選擇。對於擁有非常大編輯團隊(100多名編輯)的大學,Contentful的工作流和權限模型可能更有利。對於想要更多自訂的較小團隊,Sanity往往獲勝。
Next.js可以處理認證的學生門戶嗎? 絕對可以。Next.js支持認證頁面的服務器端渲染,而App Router的服務器組件使得在不向客戶端包公開數據的情況下獲取用戶特定數據變得直接。我們使用Auth.js與自訂提供者整合CAS(中央身份驗證服務)。門戶在沒有性能問題的情況下處理了40,000名學生。
對大學進行Drupal到Next.js遷移需要多少費用? 這個規模的項目——課程查找器、學生門戶、200多個課程、完整內容遷移、CMS設置和培訓——通常根據複雜性從$250,000到$450,000不等。但是,長期節省是顯著的。這個大學將年度維護成本從約$180K降低到$95K,這意味著該項目在3-4年內為自己買單,即使在預算範圍的高端。
大規模CMS遷移期間SEO會發生什麼? 這是一個合理的關切。我們實施了全面的重定向映射(超過2,400個301重定向),在可能的情況下保留了所有現有URL結構,並添加了Drupal網站缺少的schema.org結構化數據。啟動後前兩週,有機流量下降約8%(任何主要遷移都正常),然後恢復並在四個月內超過基線43%。
對於大學來說,Drupal 10是比使用headless更好的選擇嗎? 這取決於情況。如果你的團隊具有強大的Drupal專業知識,你的自訂模塊具有Drupal 10兼容性,並且你不需要靜態/混合網站的性能特性,Drupal 10是一條完全有效的路徑。在我們的情況下,大學失去了他們的Drupal專業知識,有23個不兼容的模塊,並且需要戲劇性的性能改進。headless方法顯然是更好的選擇。
你如何處理從Drupal到headless CMS的內容遷移? 我們使用自訂Node.js腳本,通過Drush命令導出Drupal內容,轉換數據以匹配新CMS schema,處理媒體文件遷移,並通過CMS的遷移API導入所有內容。該過程通常運行3次:一次用於初始測試,一次用於分段刷新,一次用於最終轉換。帶有嵌入式媒體的富文本內容是最困難的部分——你需要重新映射每個內部文件參考。
在遷移期間可以同時運行Drupal和Next.js嗎? 可以,我們建議這樣做。在我們的遷移期間,Drupal繼續提供生產網站,而我們在分段域上構建和測試Next.js版本。我們使用三週的雙重入境期,內容進入兩個系統。最終轉換是一個花費約15分鐘的DNS切換,Drupal在30天內保持在只讀模式作為後備。