A project exploring the integration of Edge Runtime with Next.js and MongoDB.
Next.js API 與 MongoDB 設計實作:整合 NextAuth 多元驗證方式(Credentials + OAuth)
在開發 SaaS 或工具型應用時,「使用者驗證系統」往往是關鍵基礎設施。這篇文章將分享我如何使用 Next.js(App Router)+ MongoDB + NextAuth,同時支援 Google OAuth 和 自定義帳密登入,並分享專案 mongoDB 資料結構與 API 架構。
MongoDB 資料模型設計
為了支援多元登入方式,我設計了以下幾個集合(Collections):
- users - 使用者基本資訊
1 | { |
- authProviders - 自定義驗證方式(帳密)
刷題練習 - Maximum Depth of Binary Tree 解題策略
Leetcode 題目多樣,尤其是「二元樹(Binary Tree)」相關題型,容易讓人看了發愣。
本篇文章將教你如何釐清解法邏輯,並將二元樹題型拆解為兩大類解題策略,搭配範例與模板,幫助你用 DFS(深度優先搜尋)更系統地解題
類型一:遍歷型(Traversal Type)
這類題目需要「走過整棵樹的所有節點」,通常是為了計算某種總量,例如:
- 節點總數
- 最大深度
- 所有路徑總和
- 葉子節點的數量
解題思路
可根據需求使用:
- 後序遍歷 + 回傳值 return → 適合需要組合子樹結果的情況
- 前序遍歷 + 狀態變數追蹤 → 適合需要累加或記錄路徑狀態的情況
模板(後序 + return)
1 | function dfs(node) { |
模板(前序 + 狀態變數)
1 | let result = 0; |
React 重置技巧:React Key 與 CSS offsetWidth
刷題練習 - Permutation in String
練習來自 labuladong 的演算法筆記 的滑動視窗演算法核心程式碼模板。
基本設置:
用兩個指針來界定一個區間,也就是一個「窗口」:
- 左指針 left:表示窗口的起始位置。
- 右指針 right:表示窗口的尾部(通常採用左閉右開區間 [left, right))。
窗口內的資料會隨著 left 和 right 的移動而動態改變。解題的關鍵在於:
- 擴大窗口:不斷將新的元素加入窗口,並更新窗口內的狀態或統計(例如字元出現次數)。
- 檢查窗口是否滿足條件:根據題目要求檢查當前窗口是否為合法解。
- 收縮窗口:如果當前窗口已滿足條件,嘗試從左側移除元素以找到更優(例如更短)的解,同時更新狀態。
模板
1 | var slidingWindow = function(s) { |
題目
Given two strings s1 and s2, return true if s2 contains a permutation of s1, or false otherwise.
In other words, return true if one of s1’s permutations is the substring of s2.
給定兩個字串 s1 和 s2,若 s2 中包含 s1 的某個排列,則返回 true;否則返回 false。
換句話說,若 s1 的某個排列是 s2 的子字串,則返回 true。
Example 1:
Input: s1 = “ab”, s2 = “eidbaooo”
Output: true
Explanation: s2 contains one permutation of s1 (“ba”).
side-project-zustand
這段時間在製作一個 snippet 管理的 side project,過程中遇到了一些跨頁面共享資料的需求。專案中有一包資料稱為 snippet,內部包含多個 folder 和小項目的 snippets。這些資料不僅會在 /snippet 路徑下的元件使用,還會跨 route 被其他頁面存取。
一開始,我選擇使用 React 的 Context API,透過建立一個 SnippetsContext 來管理狀態。在應用程式的最上層放置一次 SnippetsProvider,然後在需要的任何元件內直接使用 import { useSnippets } from ‘@/contexts/SnippetsContext’ 來取得 context。然而,隨著專案規模逐步擴展,這種方法逐漸顯露出其局限性。我發現即使已經有了最上層的 Provider,但在多個 dialog 或 component 中,仍然需要重複地導入 useSnippets,不僅造成代碼重複,也使得專案結構變得臃腫複雜。
為了解決上述問題,我嘗試了另一個狀態管理工具 —— Zustand。Zustand 提供了一種更直覺、更精簡的方式來建立和存取全域狀態,不需要透過 Provider 包裹元件,大幅減少了樣板代碼的使用,也讓資料流更加清晰易懂。
在這篇文章中,我會分享如何在專案中實際導入並使用 Zustand
初步實作 Zustand
1 | npm install zustand |
如果想使用持久化功能,也可以安裝:
1 | npm install zustand/middleware |
store 資料夾
在專案的 src 資料夾下建立一個 store 資料夾,這之中包含給 folder , snippet 的 slice。之所以採用 slice 是因為原本 snippet 的處理邏輯較為複雜,包含了 folder 的儲存、內部 snippet 的儲存,以及一些專門給 dialog 使用的 UI 狀態。透過將 store 分拆成多個 slice(例如 folderSlice 與 uiSlice),每個模組都只負責處理自己相關的 state 與操作,能更好地維護代碼的結構與可讀性。
1 | src/ |
在專案中的 snippet 資料夾內,會建立一個 index.ts 檔案來整合所有獨立的 slice,並導出一個完整的 Zustand store。這麼做的優點是:之後在任何元件中需要使用狀態時,只需導入整個 store,而不需要逐一引入每個 slice
1 | // src/stores/snippet/index.ts |
- 使用 zustand 提供的 persist middleware,能輕鬆將特定狀態持久化到 localStorage。
- 透過 partialize 參數,我能夠靈活地指定只有某些狀態 (例如:folders) 需要持久化,而 UI 或其他臨時狀態則保持記憶體內即可。
folderSlice.ts
在建立 folderSlice 時,透過 Zustand 的 StateCreator 定義了一個 slice,專門負責管理資料夾(folders)相關的狀態與邏輯。透過這個方式,能清楚地將資料夾的操作行為集中管理。
1 | // src/stores/snippet/slices/folderSlice.ts |
- 使用 set 來更新狀態,get 取得當前狀態。
- addFolder 方法可指定新增資料夾的位置(透過可選的 index 參數);若未指定,則預設新增到列表尾端。
snippetSlice.ts
snippetSlice 負責 snippet 的新增、刪除與更新操作。由於 snippets 存放於 folders 中,因此此 slice 會依賴 FolderSlice 的狀態來進行資料處理。
1 | // src/stores/snippet/slices/snippetSlice.ts |
新增 snippet(addSnippetToFolder)
提供一個資料夾 id 和 snippet 的內容(不含 id)。
自動產生 snippet 的 id(透過 Date.now())。
將新的 snippet 放到指定的資料夾內,並回傳完整的新 snippet,方便後續操作使用。刪除 snippet(deleteSnippetFromFolder)
根據指定的資料夾 id 和 snippet id 移除 snippet。
使用 filter 方法,確保其他 snippet 不受影響。更新 snippet(updateSnippet)
-提供 snippet id 和欲更新的部分 snippet 屬性。
-使用 map 方法,只更新符合指定 id 的 snippet,其他 snippet 維持不變。
uiSlice.ts
uiSlice 專門用來管理專案內與 UI 顯示相關的狀態,例如 dialog(對話框)的開啟或關閉狀態,以及當前匹配(matched)的 snippet 資訊。
1 | // src/stores/snippet/slices/uiSlice.ts |
- isDialogOpen
負責追蹤 dialog 是否正在顯示,便於控制 dialog 元件的顯示與隱藏。 - matchedSnippet
用於暫時儲存用戶目前匹配到的 snippet 資訊,例如 snippet 的內容、目標元素、快捷鍵資訊等等,讓 UI 能即時反應用戶操作。
在頁面中使用 Zustand 管理的狀態
想在頁面或元件內使用剛才建立的 Zustand 狀態時,只需從我們整合好的 useSnippetStore 引入所需的狀態或方法即可
1 | import { useSnippetStore } from '@/stores/snippet'; |
- 不再需要額外引入任何 Context Provider。
- 在任何需要存取全局狀態的地方,只需簡單導入 useSnippetStore,然後直接調用對應的方法或取得狀態即可。
結論
藉由導入 Zustand 並將狀態拆分成多個獨立的 Slice(folderSlice、snippetSlice、uiSlice),使我能夠:
- 清晰地分離狀態責任:每個 Slice 專注於處理特定領域的邏輯,如資料夾管理、snippet 操作,以及 UI 狀態。
- 靈活管理持久化策略:透過 Zustand 提供的 middleware(如 persist 與 partialize),只將必要的資料(例如資料夾)持久化至 localStorage,而 UI 等臨時狀態則保留在記憶體中。
- 有效降低耦合度與樣板代碼:每個元件不再需要額外引入 Provider,簡化了開發流程,並且降低未來維護成本。
figma mcp 應用
有新東西就是要來快速嘗試一下~~ 注意 cursor 要是付費版才能使用
至 GitHub clone 專案 Cursor Talk to Figma MCP
然後遵循 README:Get Started 開始
它需要安裝 bun 套件管理工具
1 | curl -fsSL https://bun.sh/install | bash |
需要在你的專案根目錄執行:
1 | cd /path/to/your/cursor-talk-to-figma-mcp |
1 | # 啟動 WebSocket server(必須持續執行) |
1 | // Add the server to your Cursor MCP configuration in ~/.cursor/mcp.json: |
以下為圖示:
Vue 動態事件名稱
在 Vue 中使用動態事件名稱的功能自 Vue 2.6.0 版本開始支持。可以使用 v-on 或 @ 來動態綁定事件名稱
簡單舉例:
根據裝置類型切換事件(桌機 vs 手機)
1 | <script setup> |
2.「檔案卡片」元件預覽或跳轉:
點擊 (click):選擇/下載檔案
滑鼠移入 (mouseover):預覽檔案摘要
1 | <template> |
1 | <script setup lang="ts"> |
注意:在「同一欄位會因不同情境而切換觸發事件」時,才建議用動態綁定。若只是單純切換事件名稱,建議直接在方法內判斷即可。
也可參考 Michael Hoffmann 這裡的另一實作方式:
https://stackblitz.com/edit/vue-tip-dynamic-event-names?file=src%2FApp.vue
twilwindCss-twMerge
在開發 React 或 Vue 應用時,動態組合 Tailwind CSS class 是常見的需求。但當 class 來源多樣(例如 props 傳入、條件渲染等),你可能會遇到 class 重複、衝突或難以維護的問題,這時候,twMerge 就能幫助你解決這些困難。
什麼是 twMerge
twMerge
是一個專為 Tailwind CSS 設計的 class 合併工
- 衝突解決:當你從不同來源(例如預設樣式和 props 傳入的 class)合併 class 時,可能會出現相同屬性的重複定義,twMerge 會自動解決這些衝突,保證最終只保留正確的 class。
- 動態組合:在實際應用中,很多時候 class 字符串是根據條件組合出來的,直接拼接可能會導致意料之外的覆蓋。twMerge 能夠讓你的 class 合併更有條理,避免混淆。
安裝方式:
1 |
|
基本使用範例:
1 |
|
SOLID 物件導向
- S 指的是 Single responsibility principle(SRP) 單一職責原則
- O 指的是 Open/close principle(OCP) 開放/封閉原則
- L 指的是 Liskov substitution principle(LSP) Liskov 替換原則
- I 指的是 Interface Segregation Principle(ISP) 介面隔離原則
- D 指的是 Dependency Inversion Principle(DIP) 依賴反轉原則
Single responsibility principle(SRP) 單一職責原則
一個 class 別應該只有一個職責,並且只應該因為一個理由而改變。
C# 程式碼範例:
以違反 SRP 的範例:
在這個範例中,User 類別同時負責使用者資料的管理、資料庫儲存、發送歡迎信和使用者驗證等多個職責。這違反了 SRP。
1 |
|
符合 SRP 的範例:
1 |
|
在這個範例中,我們將不同的職責分離到不同的類別中:
- User 類別只負責使用者資料的管理。
- UserRepository 類別負責資料庫儲存。
- EmailService 類別負責發送歡迎信。
- UserValidator 類別負責使用者驗證。
這樣做使得每個類別都只有一個職責,並且只因為一個理由而改變,符合了 SRP。
從前端的解度來理解
在前端開發中,我們經常使用元件 (Component) 的概念來構建使用者介面。一個元件通常負責渲染一部分的 UI,並處理與該部分 UI 相關的互動。這與 SRP 的概念非常相似。一個好的前端元件應該只負責單一的職責,例如:
顯示資料: 只負責接收資料並將其渲染到 UI 上。
處理使用者輸入: 只負責處理使用者在介面上的輸入事件,例如按鈕點擊、表單提交等。
發送 API 請求: 只負責向後端發送 API 請求並處理回應。