Strapi v5's Silent populate Failure with Relation Filters

Symptom: A Relation Everyone Believes Is Empty A document management print preview kept rendering “Uploader: —” for every report. The database had the data. The API returned HTTP 200 OK. Yet the uploadedBy relation came back as null, with no error or warning anywhere. Users assumed reports had no uploader assigned. Authors assumed the UI was broken. Nobody suspected the API, because it was perfectly silent. Where the Bug Lives in the Stack Before the curl session, it helps to fix where the failure happens: ...

April 24, 2026 ·  · 5 分鐘 · Peter

升級 Strapi v5.31+ 後 JWT 過期就被登出?問題藏在你沒注意到的內建路由裡

症狀:App 開啟 15 分鐘後被強制登出 使用者回報一個詭異的問題:App 正常使用一段時間後,突然被強制登出。重新登入後又好了,但過一陣子又被踢出去。 時間規律很明確——正好 15 分鐘。這恰好是我們 JWT 的過期時間。 直覺反應是去查前端的 token refresh 邏輯:interceptor 有沒有正確攔截 401?refresh token 有沒有正確儲存?重試機制有沒有 bug? 查了一輪,全部正常。 錯誤的除錯方向 這個問題前前後後花了不少時間,方向一直在前端打轉: 排查方向 結果 Flutter interceptor 邏輯 正常,有攔截 401 並觸發 refresh Token 儲存機制(SecureStorage) 正常,refresh token 有正確保存 JWT 過期時間設定 確認是 15 分鐘,符合預期 網路連線問題 排除,其他 API 都正常 每個環節看起來都沒問題,但結果就是 refresh 失敗。問題出在哪? 轉折點:看 HTTP Status Code 直到有人(或者說,終於有人)去看了 /auth/refresh 實際回傳的 HTTP status code: # JWT 過期後呼叫 refresh curl -X POST https://api.example.com/api/auth/refresh \ -H "Content-Type: application/json" \ -d '{"refreshToken": "valid-refresh-token-here"}' # 預期:200 OK(新的 JWT) # 實際:403 Forbidden 403,不是 401。 ...

February 6, 2026 ·  · 2 分鐘 · Peter

Strapi Plugin 覆寫陷阱:為什麼你的 Override 沒有生效?

前言:一個看似簡單的需求 需求很單純:讓 Email 驗證信中的連結顯示前端網址(www.example.com),而不是後台網址(api.example.com)。這樣用戶不會看到內部系統架構。 外包商寫了這段程式碼: // ❌ 看起來合理,但完全沒效果 plugin.services.user.sendConfirmationEmail = async function(user) { // 自訂邏輯... } 部署後,Email 中的連結依然指向後台。為什麼? 問題根源:Factory Function 陷阱 翻開 Strapi 原始碼,發現 plugin.services.user 不是一個物件,而是一個 factory function: // Strapi 內部實作(簡化版) plugin.services.user = (context) => { return { sendConfirmationEmail: async (user) => { /* 原始邏輯 */ }, // 其他方法... } } 這意味著什麼? 每次 Strapi 需要 user service 時,它會呼叫這個 factory function 來取得一個新的 service 實例。你直接覆蓋 sendConfirmationEmail 屬性,等於在一個 function 物件上加屬性——factory 被呼叫時根本不會讀取這個屬性。 不這樣理解會怎樣? 你會像外包商一樣,花兩天 debug 卻找不到原因,因為程式碼完全沒有報錯,只是靜靜地被忽略。 解決方案:Factory Wrapper 模式 正確的做法是包裝原本的 factory function: // ✅ Factory Wrapper 模式 const originalUserServiceFactory = plugin.services.user; plugin.services.user = (context) => { // 先取得原始 service 實例 const originalUserService = originalUserServiceFactory(context); return { ...originalUserService, // 保留所有原始方法 // 覆寫特定方法 async sendConfirmationEmail(user) { // 你的自訂邏輯 const confirmationUrl = `${FRONTEND_URL}/verifyEmail`; // ... }, }; }; 為什麼這樣有效? ...

January 15, 2026 ·  · 2 分鐘 · Peter

Strapi 自訂搜尋功能:讓 Admin 面板支援多欄位搜尋

前言 在開發 Strapi 後台管理系統時,我遇到一個實際的使用者體驗問題:在「已購買課程」列表中,管理員可以用「真實姓名」搜尋到用戶的購課記錄,但用「電話號碼」卻搜尋不到。這個不一致的行為讓管理員感到困惑。 本文將分享如何在 Strapi v5 中實作自訂搜尋邏輯,讓 Admin 面板支援跨關聯的多欄位搜尋。 問題場景 資料結構: purchased-course(已購買課程):關聯到 user user(用戶):包含 email、realName、phoneNumber、username、nickname 等欄位 問題現象: ✅ 搜尋「真實姓名」→ 可以找到對應的購課記錄 ❌ 搜尋「電話號碼」→ 找不到任何結果 為什麼會這樣? 因為 Strapi 預設的搜尋功能只會搜尋當前 Collection 的直接欄位,不會自動搜尋關聯表(relation)的欄位。當我們在 purchased-course 列表搜尋時,Strapi 只會在 purchased-course 本身的欄位中搜尋,而不會去搜尋關聯的 user 資料。 解決方案架構 Strapi 提供了三個層級可以自訂搜尋邏輯: 1. Controller 層(API 端點) 位置: src/api/purchased-course/controllers/purchased-course.ts 用途: 處理前端透過 REST API 的搜尋請求 2. Service 層(業務邏輯) 位置: src/api/purchased-course/services/purchased-course.ts 用途: 封裝可重複使用的業務邏輯 3. Content Manager Plugin Extension(Admin 面板) 位置: src/extensions/content-manager/strapi-server.ts 用途: 最重要! 這是讓 Admin 面板搜尋生效的關鍵 ...

October 27, 2024 ·  · 5 分鐘 · Peter

解決 macOS 上 Rollup rollup.darwin-arm64.node 模組錯誤與 Gatekeeper 阻擋問題

前言 使用 Apple Silicon Mac 開發前端專案時,你有沒有遇過這種情況:npm install 明明成功了,但執行 npm run dev 時 Vite 卻突然翻臉不認人,噴出一堆關於 rollup.darwin-arm64.node 的錯誤? 更慘的是,macOS 還會彈出一個視窗說「無法打開 rollup.darwin-arm64.node,因為無法驗證開發者」——彷彿你的電腦在暗示你下載了什麼奇怪的東西。 別擔心,這不是你的問題(好吧,也不完全是 Apple 的問題),而是 npm + Gatekeeper 的經典組合技。本文將帶你徹底理解並解決這個惱人的問題。 問題現象 當你在 Apple Silicon Mac 上執行 Vite/Rollup 專案時,可能會遇到以下錯誤: 錯誤 1:模組找不到 Error: Cannot find module '@rollup/rollup-darwin-arm64' Require stack: - /project/node_modules/rollup/dist/native.js 錯誤 2:系統策略拒絕載入 Error: dlopen(/project/node_modules/@rollup/rollup-darwin-arm64/rollup.darwin-arm64.node, 0x0001): code signature in (...) not valid for use in process: library load disallowed by system policy 錯誤 3:Gatekeeper 彈窗警告 macOS 直接彈出視窗:「無法打開 rollup.darwin-arm64.node,因為無法驗證開發者」 ...

January 1, 2024 ·  · 2 分鐘 · Peter