Vercel: 開發者體驗即設計

Vercel如何將開發者體驗做成產品:暗色模式優先設計、標籤狀態指示器、樂觀UI、功能性空狀態。包含CSS和JavaScript實作模式。

5 分鐘閱讀 238 字
Vercel: 開發者體驗即設計 screenshot

Vercel:以設計打造開發者體驗

「開發者對糟糕的使用者體驗過敏——如果『令人愉悅的引導流程』會拖慢他們的速度,他們根本不想要。」

Vercel 的設計哲學徹底以開發者為中心。Geist 設計系統優先考量清晰度、速度和資訊密度,而非裝飾性元素。每一個像素都服務於開發者的工作流程。


為什麼 Vercel 值得關注

Vercel 證明了開發者工具可以擁有出色的設計,而不必顯得「過度設計」。儀表板快速、資訊密集,且不會干擾工作。

主要成就: - 創造了 Geist,一款專為開發者設計的字體 - 儀表板重新設計後,首次有意義繪製(First Meaningful Paint)減少了 1.2 秒 - 在開發者工具領域率先採用深色模式優先的設計 - 樹立了部署使用者體驗的標準 - 分頁圖示會反映部署狀態(建置中、錯誤、就緒)


重點摘要

  1. 深色模式是一種尊重,而非功能 - 開發者在終端機和 IDE 中使用深色背景工作;白色儀表板會造成刺眼的情境切換和眼睛疲勞
  2. 狀態應該無處不在 - 分頁 favicon、頁面標題、時間軸圓點:部署狀態應該在不切換焦點或開啟分頁的情況下就能看到
  3. 樂觀式 UI 消除感知延遲 - 立即顯示預期狀態,在背景與實際狀態同步;開發者會注意到 300 毫秒的延遲
  4. 空白狀態是指示,而非插圖 - 顯示要執行的確切指令(git push origin main),而非帶有「開始使用」按鈕的裝飾性圖形
  5. 效能就是設計 - Vercel 的儀表板重新設計後,首次有意義繪製減少了 1.2 秒;再美的動畫也無法彌補緩慢的載入時間

核心設計哲學

以開發者為中心的原則

開發者評判產品的標準是它拖慢他們多少。Vercel 的設計反映了這一點:

反面模式(開發者討厭的)                VERCEL 的做法
───────────────────────────────────────────────────────────────────
增加延遲的「令人愉悅」動畫              即時、無過渡的狀態
阻擋工作的引導精靈                      CLI 優先,儀表板可選
隱藏在分頁中的密集文件                  資訊一目了然
每個操作都有載入轉圈                    樂觀更新 + SWR
儀表板中的行銷文案                      純功能性 UI

關鍵洞察:開發者不想被「取悅」。他們想要發布產品。


模式庫

1. 深色模式的卓越表現

Vercel 的深色模式不是一個切換選項。它是預設值。設計精準如手術刀:純粹的黑白創造最大對比度。

色彩哲學:

:root {
  /* Vercel 的調色板極為簡潔 */

  /* 背景 - 純黑,無灰色 */
  --bg-000: #000000;
  --bg-100: #0A0A0A;
  --bg-200: #111111;

  /* 前景 - 高對比度白色 */
  --fg-100: #FFFFFF;
  --fg-200: #EDEDED;
  --fg-300: #A1A1A1;
  --fg-400: #888888;

  /* 邊框 - 微妙但可見 */
  --border-100: #333333;
  --border-200: #444444;

  /* 語意化 - 部署狀態 */
  --color-success: #00DC82;  /* 綠色 - 已部署 */
  --color-error: #FF0000;    /* 紅色 - 失敗 */
  --color-warning: #FFAA00;  /* 琥珀色 - 建置中 */
  --color-info: #0070F3;     /* 藍色 - 排隊中 */

  /* 強調色 - Vercel 的標誌性色彩 */
  --accent: #FFFFFF;         /* 黑底上以白色作為強調色 */
}

為什麼純黑有效: - 文字可讀性的最大對比度 - 開發者信任的終端機風格美學 - 在暗光環境中減少眼睛疲勞 - 讓彩色狀態指示器更加突出


2. 分頁狀態指示器

Vercel 在瀏覽器分頁圖示中反映部署狀態,即使分頁未獲得焦點,資訊仍然可見。

┌─ 瀏覽器分頁列 ─────────────────────────────────────────────────────┐
│                                                                    │
│  [▶] acme-web - Building    [✓] blog - Ready    [✕] api - Error   │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

分頁圖示狀態:
  ⏳ 排隊中(灰色圓圈)
  ▶  建置中(動態轉圈)
  ✓  就緒(綠色勾號)
  ✕  錯誤(紅色 X)

實作模式:

// 根據部署狀態動態更新 favicon
function updateFavicon(status) {
  const link = document.querySelector("link[rel~='icon']");

  const icons = {
    queued: '/favicon-queued.svg',
    building: '/favicon-building.svg',  // 動態
    ready: '/favicon-ready.svg',
    error: '/favicon-error.svg',
  };

  link.href = icons[status];
}

// 標題也反映狀態
function updateTitle(projectName, status) {
  const prefixes = {
    queued: '⏳',
    building: '▶',
    ready: '✓',
    error: '✕',
  };

  document.title = `${prefixes[status]} ${projectName} - Vercel`;
}

關鍵洞察:開發者會開啟許多分頁。狀態在分頁列中可見,意味著他們不必切換分頁就能檢查建置狀態。


3. 部署時間軸

部署檢查器顯示部署過程的清晰時間軸。

┌─ 部署時間軸 ───────────────────────────────────────────────────────┐
│                                                                    │
│  [o] 排隊中                                  12:34:56 PM           │
│  │                                                                 │
│  [o] 建置中                                  12:34:58 PM           │
│  │ └─ 安裝相依套件... 3.2s                                         │
│  │ └─ 建置中... 12.4s                                              │
│  │ └─ 產生靜態頁面... 2.1s                                         │
│  │                                                                 │
│  [o] 部署中                                  12:35:14 PM           │
│  │ └─ 上傳建置產出...                                              │
│  │                                                                 │
│  [*] 就緒                                    12:35:18 PM           │
│    └─ https://acme-abc123.vercel.app                               │
│                                                                    │
│  總計:22s                                                         │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

視覺編碼:

.timeline-step {
  position: relative;
  padding-left: 24px;
}

.timeline-step::before {
  content: '';
  position: absolute;
  left: 0;
  top: 6px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--step-color);
}

/* 連接線 */
.timeline-step:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 4px;
  top: 16px;
  width: 2px;
  height: calc(100% - 6px);
  background: var(--border-100);
}

/* 步驟狀態 */
.timeline-step[data-status="complete"]::before {
  background: var(--color-success);
}

.timeline-step[data-status="active"]::before {
  background: var(--color-warning);
  animation: pulse 1.5s infinite;
}

.timeline-step[data-status="error"]::before {
  background: var(--color-error);
}

.timeline-step[data-status="pending"]::before {
  background: var(--fg-400);
}

4. 日誌檢視器設計

Vercel 的日誌檢視器整合在部署概覽中,而非獨立頁面。

┌─ Build Logs ───────────────────────────────────────────────────────┐
│                                                                    │
│  Filter: [All ▼]  [Function: api/hello ▼]           [Copy] [↓]    │
│                                                                    │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  12:34:58.123  info   Installing dependencies...                   │
│  12:35:01.456  info   added 1234 packages in 3.2s                  │
│  12:35:01.789  info   Running build...                             │
│  12:35:14.012  info   ✓ Compiled successfully                      │
│  12:35:14.234  warn   Large bundle size: pages/index.js (245kb)    │
│  12:35:14.567  info   Generating static pages...                   │
│  12:35:16.890  info   ✓ Generated 42 pages                         │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

主要功能: - 一鍵複製到剪貼簿 - 依函式或建置輸出篩選 - 日誌等級以顏色區分(info、warn、error) - 毫秒精度的時間戳記 - 可分享特定日誌行的 URL

實作方式:

.log-line {
  display: flex;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  padding: 2px 12px;
}

.log-line:hover {
  background: var(--bg-200);
}

.log-timestamp {
  color: var(--fg-400);
  min-width: 100px;
  margin-right: 12px;
}

.log-level {
  min-width: 48px;
  margin-right: 12px;
}

.log-level[data-level="info"] { color: var(--fg-300); }
.log-level[data-level="warn"] { color: var(--color-warning); }
.log-level[data-level="error"] { color: var(--color-error); }

.log-message {
  color: var(--fg-100);
  white-space: pre-wrap;
  word-break: break-word;
}

5. 空狀態

Vercel 的空狀態是功能導向的,而非裝飾性的。它們告訴你下一步該做什麼。

┌─ Empty State: No Deployments ──────────────────────────────────────┐
│                                                                    │
│                                                                    │
│                         No deployments yet                         │
│                                                                    │
│               Push to your repository to create                    │
│                    your first deployment                           │
│                                                                    │
│                                                                    │
│           git push origin main                                     │
│                                                                    │
│                                                                    │
│                         [View Documentation]                       │
│                                                                    │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

設計原則: - 不使用裝飾性插圖 - 明確的操作指引(git 指令) - 提供文件連結 - 指令使用等寬字體(方便複製)


視覺設計系統

字體排版(Geist)

Vercel 專為開發者體驗打造了 Geist 字體:

:root {
  /* Geist Sans - UI 與內文 */
  --font-sans: 'Geist', -apple-system, BlinkMacSystemFont, sans-serif;

  /* Geist Mono - 程式碼與技術內容 */
  --font-mono: 'Geist Mono', 'SF Mono', monospace;

  /* 字級比例 */
  --text-xs: 12px;
  --text-sm: 13px;
  --text-base: 14px;
  --text-lg: 16px;
  --text-xl: 18px;
  --text-2xl: 24px;

  /* 行高 */
  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-relaxed: 1.75;

  /* 字距 */
  --tracking-tight: -0.02em;
  --tracking-normal: 0;
  --tracking-wide: 0.02em;
}

/* 表格數字用於資料顯示 */
.tabular-nums {
  font-variant-numeric: tabular-nums;
}

/* 或使用 Geist Mono 進行數值比較 */
.data-value {
  font-family: var(--font-mono);
}

間距系統

:root {
  /* 4px 基礎單位 */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
}

Border Radius

:root {
  /* Subtle, consistent radii */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-xl: 12px;
  --radius-full: 9999px;
}

動畫模式

樂觀更新

Vercel 採用樂觀式 UI 更新。操作感覺即時完成。

// SWR pattern for realtime updates
const { data, mutate } = useSWR('/api/deployments');

async function triggerDeploy() {
  // Immediately show "deploying" state
  mutate(
    { ...data, status: 'building' },
    false  // Don't revalidate yet
  );

  // Then actually trigger
  await fetch('/api/deploy', { method: 'POST' });

  // Revalidate to get real state
  mutate();
}

細膩的載入狀態

/* Skeleton loading - no spinners */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--bg-200) 0%,
    var(--bg-100) 50%,
    var(--bg-200) 100%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: var(--radius-md);
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

按鈕狀態

.button {
  transition: background 100ms ease, transform 100ms ease;
}

.button:hover {
  background: var(--fg-100);
}

.button:active {
  transform: scale(0.98);
}

/* No long transitions - instant feedback */

效能優化(設計導向)

Vercel 的儀表板重新設計包含了能提升效能的設計決策:

使用的技術: - 預先連接到 API、Assets 和 Avatar 來源 - 關鍵 API 請求獲得更高的瀏覽器優先級 - 使用 useMemo、useCallback 記憶化 React 元件 - ReactDOM.unstable_batchedUpdates 減少了 20% 的重新渲染 - 使用 SWR 實現高效的即時資料更新

關鍵洞察:效能即是設計。一個有華麗動畫但緩慢的儀表板,比一個沒有動畫但快速的儀表板更糟。


對我們工作的啟示

1. 深色模式作為預設

當你的使用者在深色環境中工作(終端機、IDE),深色模式不是功能——而是尊重。

2. 狀態無處不在

分頁圖示、頁面標題、時間軸指示器:狀態應該在不需要聚焦的情況下就能看見。

3. 預設採用樂觀更新

立即顯示預期狀態。在背景中更新實際結果。

4. 開發者討厭等待

盡可能不使用載入旋轉圖示。使用骨架狀態、樂觀更新、預先載入。

5. 空白狀態即是指引

不要顯示漂亮的插圖。顯示他們需要執行的指令。


常見問題

為什麼 Vercel 使用純黑色 (#000000) 而非深灰色作為背景?

純黑色為白色文字提供最大對比度,創造最佳可讀性。它也符合開發者已經在使用的終端機和程式碼編輯器的美學,讓儀表板感覺像是他們工作流程的原生部分。深灰色背景通常感覺「更柔和」,但會降低對比度,在高解析度螢幕上可能顯得模糊。

Vercel 的分頁狀態指示器是如何運作的?

Vercel 根據部署狀態動態更新瀏覽器 favicon:建置中顯示旋轉圖示、就緒顯示綠色勾號、錯誤顯示紅色叉號。頁面標題也會更新 emoji 前綴(▶、✓、✕)。這意味著開發者可以在多個分頁中監控多個部署而無需切換焦點——在瀏覽器分頁列中一目了然。

Vercel 對載入狀態的處理方式是什麼?

Vercel 避免使用傳統的載入旋轉圖示,轉而採用樂觀 UI 和骨架螢幕。當你觸發部署時,UI 會在伺服器確認之前立即顯示「建置中」狀態。SWR 函式庫處理背景重新驗證。這使得操作感覺即時,即使網路請求需要 200-500 毫秒。

什麼是 Geist?為什麼 Vercel 要創建自訂字型?

Geist 是 Vercel 專為開發者介面設計的字型家族。它包括用於 UI 文字的 Geist Sans 和用於程式碼的 Geist Mono。該設計針對儀表板中常見的小尺寸(12-14px)進行優化,包含用於對齊資料欄的等寬數字,並具有獨特的字元形狀以防止相似字形之間的混淆(l、1、I)。

Vercel 處理空白狀態的方式與其他產品有何不同?

Vercel 的空白狀態顯示可執行的指令,而非裝飾性插圖。空的部署頁面會以等寬字型顯示 git push origin main(便於複製),而不是帶有通用「開始使用」按鈕的卡通圖片。這種理念是:開發者想要確切知道該做什麼,而不是被視覺效果安撫。