解決 CKEditor 圖片水平排版在前端顯示為垂直排列的問題

前言:編輯器與前端的排版不一致之謎 在開發多平台醫療健康應用時,我們採用了現代化的技術棧: 後端 CMS:Strapi v5.15.1(Headless CMS) 前端框架:Vue.js 3 富文本編輯器:CKEditor 5 資料傳輸:GraphQL API 這個組合在大多數情況下運作良好,編輯者可以在 Strapi 後台使用 CKEditor 輕鬆編輯富文本內容,前端 Vue.js 應用透過 GraphQL 獲取並渲染這些內容。 然而,我們遇到了一個令人困惑的問題: 在 Strapi 後台使用 CKEditor 精心排版的水平並排圖片,到了前端網頁卻變成了垂直排列。 這個問題不僅影響了內容的視覺呈現,也破壞了編輯者的排版意圖。更重要的是,這讓非技術背景的內容編輯者感到困惑:「為什麼我在後台看到的排版,到了網站上就變了?」 這篇文章將深入探討這個問題的根本原因,並提供系統性的解決方案。 問題現象與環境說明 內容流程架構 我們的內容從編輯到展示的完整流程如下: sequenceDiagram participant Editor as 內容編輯者 participant Strapi as Strapi 後台 participant CKEditor as CKEditor 5 participant DB as 資料庫 participant GraphQL as GraphQL API participant Vue as Vue.js 前端 participant Browser as 使用者瀏覽器 Editor->>Strapi: 登入後台編輯內容 Strapi->>CKEditor: 載入富文本編輯器 Editor->>CKEditor: 編輯內容,設定圖片水平排列 CKEditor->>CKEditor: 生成 HTML(含 float 樣式) CKEditor->>Strapi: 儲存富文本內容 Strapi->>DB: 儲存到資料庫 Note over Editor,DB: 編輯階段完成 Vue->>GraphQL: 請求內容資料 GraphQL->>DB: 查詢內容 DB-->>GraphQL: 回傳富文本 HTML GraphQL-->>Vue: 回傳 JSON(含 HTML 字串) Vue->>Vue: 使用 v-html 渲染 HTML Vue->>Browser: 顯示最終頁面 Note over Browser: ❌ 問題:圖片垂直排列<br/>(預期:水平排列) 問題的具體表現 預期行為: ...

August 1, 2025 · 11 min · Peter

前端登入失敗的真兇:深入理解 CORS 問題與實戰解法

前言:那個令人抓狂的錯誤訊息 在開發前後端分離的 Web 應用時,幾乎每位工程師都曾遇過這個令人頭痛的錯誤: Access to fetch at 'http://localhost:1337/api/auth/local' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 這個錯誤通常發生在最關鍵的時刻: 前端登入功能即將完成,卻無法呼叫後端 API 串接第三方服務時,資料無法正常取得 部署到測試環境後,原本運作正常的功能突然失效 CORS(Cross-Origin Resource Sharing,跨來源資源共用)是現代 Web 開發中的核心安全機制,但也是許多開發者的痛點。這篇文章將從基礎原理到實戰應用,帶你完整理解 CORS 的運作方式,並提供實際可用的解決方案。 為什麼需要 CORS?從同源政策說起 同源政策的誕生 在理解 CORS 之前,我們需要先認識「同源政策」(Same-Origin Policy, SOP)。這是瀏覽器最基本的安全機制,在 1995 年 Netscape Navigator 2.0 引入 JavaScript 時就已經存在。 同源政策的目的:防止惡意網站讀取另一個網站的敏感資料。 想像一個情境:你登入了網路銀行(https://bank.com),此時你的瀏覽器保存了銀行的登入 Cookie。如果沒有同源政策,當你不小心訪問了一個惡意網站(https://evil.com),該網站的 JavaScript 就能透過你的瀏覽器向 https://bank.com 發送請求,並讀取你的帳戶資料。 同源政策阻止了這種攻擊:https://evil.com 的 JavaScript 無法讀取 https://bank.com 的回應內容。 什麼是「同源」? 兩個 URL 被視為同源,必須滿足以下三個條件: ...

July 1, 2025 · 10 min · Peter

整合 Google 登入到 Strapi:在 Kubernetes 上攻克「Secure Cookie over Unencrypted Connection」的心路歷程

前言 在現代 Web 應用開發中,提供第三方登入(Social Login)已經成為標準配備。相比傳統的帳號密碼註冊流程,使用 Google、Facebook、GitHub 等服務登入不僅能降低使用者註冊門檻,還能提升安全性(由大廠處理密碼儲存與驗證)。 當我們決定為 Strapi CMS 後台加入 Google OAuth 登入時,原本以為只是個簡單的設定任務: 在 Google Cloud Console 建立 OAuth 2.0 憑證 在 Strapi 填入 Client ID 和 Client Secret 點擊「Login with Google」按鈕,完成! 但現實總是更複雜。當應用部署到 Kubernetes 叢集後,我們遇到了一個令人困惑的錯誤訊息: Error: Cannot send secure cookie over unencrypted connection 這個錯誤訊息背後,牽涉到 HTTP/HTTPS 協定、Proxy Trust 機制、Kubernetes Ingress 架構、以及瀏覽器 Cookie 安全策略等多層知識。這篇文章將完整記錄我如何一步步拆解問題、理解根本原因、並最終在生產環境中實現安全可靠的 Google 登入功能。 OAuth 2.0 授權碼流程基礎 在深入問題之前,我們先理解 Google OAuth 登入的完整流程。OAuth 2.0 提供了多種授權模式(Grant Types),而 Web 應用最常使用的是「授權碼模式(Authorization Code Flow)」。 sequenceDiagram participant U as 使用者瀏覽器 participant S as Strapi CMS<br/>(你的應用) participant G as Google OAuth Server participant A as Google Authorization Server Note over U,A: 第一階段:取得授權碼 U->>S: 1. 點擊「Login with Google」 S->>U: 2. 重導向到 Google 授權頁 Note right of S: redirect_uri=https://cms.example.com/api/connect/google/callback U->>G: 3. GET /o/oauth2/v2/auth?client_id=...&redirect_uri=... G->>U: 4. 顯示 Google 登入頁面 U->>G: 5. 輸入帳號密碼,同意授權 G->>U: 6. 重導向回應用 (帶著授權碼) Note right of G: https://cms.example.com/api/connect/google/callback?code=AUTH_CODE Note over U,A: 第二階段:用授權碼換取存取權杖 U->>S: 7. GET /api/connect/google/callback?code=AUTH_CODE S->>A: 8. POST /token (帶著 code + client_secret) A->>S: 9. 回傳 access_token + id_token S->>A: 10. 驗證 id_token,取得使用者資料 A->>S: 11. 回傳使用者 email, name 等資訊 Note over U,S: 第三階段:建立 Session (問題發生點!) S->>S: 12. 建立或更新 Strapi 使用者記錄 S->>S: 13. 建立 Session,準備設定 Cookie Note right of S: ⚠️ 這裡檢查連線是否為 HTTPS S->>U: 14. Set-Cookie: strapi_session=...; Secure; HttpOnly U->>S: 15. 後續請求帶著 Cookie 這個流程中有幾個關鍵點: ...

May 16, 2025 · 18 min · Peter

解決 Strapi CMS 正式環境空白頁的踩坑經驗分享

前言:一個簡單的環境變數引發的災難 在部署 Strapi CMS 到 Kubernetes 正式環境時,只是加了一行看似無害的環境變數設定: env: - name: NODE_ENV value: production # 就是這一行! 結果卻導致整個管理後台變成一片空白,連登入頁面都看不到。更詭異的是: ✅ API 完全正常,GraphQL 和 REST 都能回應 ✅ Pod 狀態正常,沒有任何錯誤訊息 ✅ 日誌顯示 Strapi 成功啟動 ❌ 瀏覽器打開 /admin 卻是一片空白 這種「Schrodinger 的服務」(同時正常又不正常)讓人抓狂。經過一番排查,終於發現罪魁禍首是 CSP (Content Security Policy) 在作怪。 本文將深入探討: 為什麼正式環境會出現空白頁 CSP 的工作原理與安全機制 完整的問題排查步驟 如何正確配置 Strapi 的安全策略 生產環境的安全最佳實踐 問題背景:開發正常,正式環境空白 環境差異對比 graph LR subgraph "開發環境 (Development)" D1[NODE_ENV=development] D1 --> D2[✅ CSP 寬鬆] D1 --> D3[✅ 詳細錯誤訊息] D1 --> D4[✅ Hot Reload] D2 --> D5[✅ Admin Panel 正常] end subgraph "正式環境 (Production)" P1[NODE_ENV=production] P1 --> P2[🔒 CSP 嚴格] P1 --> P3[⚠️ 精簡錯誤訊息] P1 --> P4[📦 壓縮打包] P2 --> P5[❌ Admin Panel 空白] end style D5 fill:#22c55e style P5 fill:#ef4444 問題現象詳細描述 Kubernetes Deployment 設定: ...

May 7, 2025 · 9 min · Peter

解決 Kubernetes 多餘 Pod 問題與 CrashLoopBackOff 的實戰心得

前言:一次神秘的 Pod 複製事件 在一次例行的 Strapi CMS 更新部署到 AWS EKS 時,遇到了一個詭異的現象:明明 Deployment 設定檔中清楚寫著 replicas: 1,但實際運行的 Pod 卻有兩個!更奇怪的是,其中一個 Pod 持續處於 CrashLoopBackOff 狀態,而另一個則正常運行。 無論怎麼刪除多餘的 Pod,它總是會像打不死的蟑螂一樣再次出現。這種「靈異事件」讓我開始懷疑 Kubernetes 是不是有自己的想法… 本文將深入探討: 為什麼會出現多餘的 Pod CrashLoopBackOff 背後的機制 Kubernetes Deployment 和 ReplicaSet 的運作原理 實戰排查步驟與解決方案 Secret 編碼陷阱與預防措施 問題背景:Deployment 說一個,實際卻有兩個 問題現象 預期行為: # my-strapi-prod-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-strapi-prod spec: replicas: 1 # 只要 1 個 Pod selector: matchLabels: app: my-strapi-prod 實際情況: $ kubectl get pods -n default | grep my-strapi-prod NAME READY STATUS RESTARTS AGE my-strapi-prod-7d4b5c8f9d-x2k4p 1/1 Running 0 10m my-strapi-prod-8c9a6d7e5f-w8n2q 0/1 CrashLoopBackOff 5 5m 兩個 Pod 同時存在,卻只有一個正常運行! ...

May 6, 2025 · 9 min · Peter

Strapi 5

客製化UI & Components Strapi 5 客製化UI & Components 發現: https://design-system.strapi.io/?path=/docs/getting-started-welcome–docs 在@strapi/design-system, 目前無法更改底層 https://docs.strapi.io/dev-docs/customization “Some parts of the admin panel can be customized.” 覆蓋樣式:使用自定義 CSS 或樣式覆蓋,而非直接改動源碼。(目前朝此方向研究)

January 15, 2025 · 1 min · Peter