HeyBinyang

MVP 不是將就,是一種更聰明的開發順序

·技術·visibility10 閱讀 ·開發

作為工程師,我們天生喜歡「做完整」:設計優雅的架構、抽象通用元件、配齊權限驗證、CI/CD、一套完善的設計系統……結果上線時間一拖再拖。

MVP(Minimum Viable Product,最小可行產品)這套思維,本質上是幫你用更小的代價驗證一個想法值不值得繼續投入

對開發者來說,它不是「寫爛一點的程式碼」,而是「在正確的時間做剛剛好的工程決策」。

先說清楚:什麼是 MVP?

比較正式一點的定義是:MVP 是一個功能最小、但足以交付給真實用戶使用,並用來收集回饋、驗證假設的產品版本。

這裡有三個關鍵資訊:

  • 它是真正可用的產品,而不是原型圖或 PPT。

  • 目標是「學習」和「驗證」,不是追求一次性做成完美成品。

  • 它應該以最小的實現成本,獲得盡可能多的用戶行為回饋。

簡化成一句話:用最小的一堆程式碼,弄清楚一件事——這個東西有沒有人真正想用。

MVP 開發的核心思維

站在工程視角,可以把 MVP 思維拆成幾步:

  1. 明確要驗證的「一個問題」

    例如:「用戶是否願意用一個極簡線上記事工具,而不是繼續用微信發給自己、發郵箱。」

  2. 只保留驗證這個問題必須的功能

    建立筆記、列表展示、刪除一條——夠了。

  3. 用你最快的路徑實現

    不一定是「最潮技術棧」,而是你最熟的那一套。

  4. 在最小範圍上線,觀察真實使用

    先丟給身邊的同事、朋友,再考慮公開發布。

  5. 基於行為數據迭代,而不是基於腦補加需求

    看大家是否會回來繼續用,而不是聽他們說「不錯不錯」。

一個簡單對比:工程師常見兩條路線

非 MVP 路線(也就是我們經常走的那條):

  • 一開始就設計完整數據模型和複雜關聯

  • 規劃多角色、多權限體系

  • 搭建完整權限驗證、支付、通知、日誌、監控

  • 做完 UI 元件庫和主題系統

  • 半年後才準備打開 /signup 頁面給別人看

MVP 路線

  • 數據模型只保留核心表格

  • 先不做註冊登入,用簡單方式區分用戶或乾脆單用戶

  • 一個列表頁 + 一個建立頁

  • 一週內丟給 5–10 個真實用戶用用看

架構可以後面補回來,但要是沒人願意用,你就少搭了一整座「空城」。

實戰:用 Next.js 做一個記事類 MVP

下面用一個「線上筆記/備忘錄」作為範例,用 Next.js(App Router)走完一次 MVP 思路。

1. 先定義 MVP 範圍

需求刻意壓到很小:

  • 用戶可以在瀏覽器裡輸入一條文字筆記

  • 點擊儲存後,頁面上能看到這條筆記

  • 可以刪除某條筆記

  • 數據儲存先用記憶體或簡單 JSON 檔案(甚至瀏覽器 localStorage 也行)

暫時不做的東西:

  • 用戶註冊/登入

  • 標籤、搜尋、富文字、排序

  • 多端同步、行動端適配

  • 精美 UI

這樣一個範圍,對任何熟悉 React 的開發者來說,1–2 天就可以搞出一個可用版本。

2. 專案腳手架與基本結構

用官方文件推薦方式建立 App Router 專案。

在終端機中:

npx create-next-app@latest note-mvp
# 或者選擇 TypeScript、App Router 等預設選項
cd note-mvp
npm run dev

目錄結構中,app/ 目錄是核心。 [nextjs](https://nextjs.org/docs/app)

我們可以這樣安排最小結構:

  • app/page.tsx:首頁,顯示筆記列表和建立表單

  • app/api/notes/route.ts:一個簡單 API,用於建立/取得/刪除筆記(使用記憶體或簡易儲存) [nextjs](https://nextjs.org/docs/app)

3. 用 App Router 寫一個極簡 API

app/api/notes/route.ts 中,先用記憶體陣列模擬儲存(真實環境你可以換成 SQLite / Supabase 等)。

// app/api/notes/route.ts
import { NextResponse } from 'next/server'

type Note = {
  id: string
  content: string
  createdAt: string
}

let notes: Note[] = []

export async function GET() {
  return NextResponse.json(notes)
}

export async function POST(request: Request) {
  const { content } = await request.json()
  if (!content || typeof content !== 'string') {
    return new NextResponse('Invalid content', { status: 400 })
  }

  const note: Note = {
    id: crypto.randomUUID(),
    content,
    createdAt: new Date().toISOString(),
  }

  notes.unshift(note)
  return NextResponse.json(note, { status: 201 })
}

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url)
  const id = searchParams.get('id')
  if (!id) {
    return new NextResponse('Missing id', { status: 400 })
  }

  notes = notes.filter((n) => n.id !== id)
  return new NextResponse(null, { status: 204 })
}

這段程式碼有很多「可以優化」的點,比如:

  • 數據只在記憶體裡,程序重啟就沒了

  • 沒有並發安全,也沒有權限驗證

但在 MVP 階段,它已經滿足了「驗證:有人願意寫點什麼並回來看」的目標。

4. 寫一個簡單的頁面

app/page.tsx 中,用 Client Component 處理互動。

'use client'

import { useEffect, useState } from 'react'

type Note = {
  id: string
  content: string
  createdAt: string
}

export default function Home() {
  const [notes, setNotes] = useState<Note[]>([])
  const [input, setInput] = useState('')
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    fetch('/api/notes')
      .then((res) => res.json())
      .then((data) => setNotes(data))
  }, [])

  async function handleAdd(e: React.FormEvent) {
    e.preventDefault()
    if (!input.trim()) return

    setLoading(true)
    try {
      const res = await fetch('/api/notes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content: input.trim() }),
      })
      const note: Note = await res.json()
      setNotes((prev) => [note, ...prev])
      setInput('')
    } finally {
      setLoading(false)
    }
  }

  async function handleDelete(id: string) {
    await fetch(`/api/notes?id=${id}`, { method: 'DELETE' })
    setNotes((prev) => prev.filter((n) => n.id !== id))
  }

  return (
    <main style={{ maxWidth: 600, margin: '2rem auto', padding: '0 1rem' }}>
      <h1>Minimal Notes MVP</h1>

      <form onSubmit={handleAdd} style={{ marginBottom: '1rem' }}>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          rows={3}
          style={{ width: '100%', marginBottom: '0.5rem' }}
          placeholder="寫點什麼..."
        />
        <button type="submit" disabled={loading}>
          {loading ? '儲存中...' : '儲存'}
        </button>
      </form>

      <section>
        {notes.length === 0 && <p>還沒有任何筆記。</p>}
        {notes.map((note) => (
          <article
            key={note.id}
            style={{
              border: '1px solid #ddd',
              padding: '0.75rem',
              marginBottom: '0.75rem',
            }}
          >
            <p style={{ whiteSpace: 'pre-wrap' }}>{note.content}</p>
            <small style={{ color: '#666' }}>
              {new Date(note.createdAt).toLocaleString()}
            </small>
            <div>
              <button
                onClick={() => handleDelete(note.id)}
                style={{ marginTop: '0.25rem' }}
              >
                刪除
              </button>
            </div>
          </article>
        ))}
      </section>
    </main>
  )
}

這個頁面非常「樸素」:一個 textarea,一個按鈕,一個列表。

但它已經完整打通了一個閉環:

  • 用戶能建立一條筆記

  • 能看到自己的歷史筆記

  • 能刪除不想要的

從 MVP 角度看,這已經足以拿給 5–10 個測試用戶去試用,看看他們的真實行為。

這個 Next.js MVP 範例體現了哪些 MVP 思維?

圍繞剛才這段實現,我們可以反過來看:

  • 技術棧足夠簡單

沒有額外引入狀態管理庫、UI 框架、複雜後端服務。

  • 架構允許未來擴展

雖然現在用的是記憶體儲存,但 API 路徑已經固定,將來換成資料庫只要改 route 裡的實現即可。

  • 功能只夠用來驗證一個核心問題

「有人會願意在瀏覽器裡寫一條簡短筆記,並在同一入口回來查看/刪除嗎?」

  • 成本可控

以一個熟悉 Next.js 的工程師水準,這個 MVP 級實現一天內可以完成並部署到 Vercel。

從這個起點開始,你可以按用戶行為來決定後續路線,例如:

  • 如果用戶大量回饋「想搜尋」,再考慮加入搜尋

  • 如果大家抱怨「換裝置就看不到」,再加持久化儲存和登入

  • 如果發現根本沒人回來第二次,那可能需要回頭審視需求本身

常見誤區:把「工程完美」當成目標

在實際專案中,很多工程師會把這類 MVP 當成「臨時玩具」,心裡過不去:

  • 「我不想寫這種簡陋實現,以後還要重構兩次」

  • 「不做權限驗證上線太不專業了」

  • 「不用某某最佳實踐框架感覺不安心」

這裡有兩個現實考慮:

  • 如果驗證下來「沒人用」,你已經省下了所有後續重構成本

  • 如果驗證下來「有人真用」,那你至少有一份真實的需求和行為數據,可以支持你去做工程級優化

MVP 並不是否定工程品質,而是把「高品質工程」放在需求被證明有效之後再做。

寫在最後:給工程師的 MVP 心態

可以把 MVP 思維理解成一種工程風險管理

  • 在用戶還沒證明「值得你為他蓋城堡」之前,用一頂帳篷就夠了

  • 你完全可以用 Next.js 這種現代框架搭建出「帳篷」,而不是直接修一個城堡

對有經驗的開發者來說,一個實用的做法是:

  • 先用你最熟的棧(比如 Next.js + 一個簡單資料庫)做出一個「能跑的骨架」

  • 找到 3–10 個真實用戶,觀察他們一個月的行為

  • 只為「被真實行為證明重要的東西」做架構升級

分享