Next.js+AI实战:从零三天上线Product Hunt的极简智能摘要工具
一个极简智能摘要工具从零开发到上线ProductHunt仅用三天,获得当日Top5。产品仅含三个功能:粘贴长文、AI生成三句话摘要、一键复制。采用Next.js14作为前后端框架,后端通过OpenAI流式接口透传SSE实现打字机效果,前端逐字渲染,样式克制,部署于Vercel,聚焦核心功能并控制成本。
上个月我开发了一款基于AI的三句话摘要工具,用户只需粘贴一篇长文,AI即可智能生成精准摘要。没有复杂的后台管理系统,没有用户体系,没有付费墙,仅需一个输入框、一个按钮,即可获得一段漂亮的输出。这款工具仅用三天完成开发,第四天上线Product Hunt,当天便获得了Top 5的成绩。本文完整记录技术实现过程,代码虽不多,但每一行都经过反复删减,保留最核心部分。
一、产品设计:少即是多
1.1 功能清单
整个AI摘要工具仅包含三个功能:
- 粘贴或输入长文本
- 点击按钮,AI生成三句话摘要
- 一键复制摘要
极致简洁,不附带任何冗余功能。
graph LRA["用户输入长文"] --> B["调用 AI API"]B --> C["流式输出摘要"]C --> D["一键复制"]style B fill:#8b5cf6,color:#fff
1.2 技术选型
| 层 | 选择 | 理由 |
|---|---|---|
| 框架 | Next.js 14 (App Router) | 前后端一体,Route Handler 直接当 API |
| 样式 | CSS Modules | 不引入额外依赖,够用就好 |
| AI | OpenAI gpt-4o-mini | 便宜、快、摘要质量够 |
| 部署 | Vercel | 零配置,推完代码自动上线 |
独立开发的一个原则:能少引一个依赖就少引一个。每多一个 npm install,就多一份未来的维护债。
二、核心实现
2.1 项目结构
summarizer/├── app/│ ├── layout.tsx # 全局布局│ ├── page.tsx # 首页(唯一页面)│ ├── page.module.css # 首页样式│ └── api/│ └── summarize/│ └── route.ts # AI 摘要接口├── components/│ ├── TextInput.tsx # 输入框组件│ ├── SummaryOutput.tsx # 摘要输出组件│ └── CopyButton.tsx # 复制按钮├── lib/│ └── openai.ts # OpenAI 封装└── package.json
一共8个文件,每个文件承担明确职责,不多不少。
2.2 后端:流式 AI 接口
// app/api/summarize/route.tsimport { NextRequest } from 'next/server';export async function POST(req: NextRequest) { const { text } = await req.json(); // 输入校验:不废话,直接拦 if (!text || text.length < 50) { return new Response(JSON.stringify({ error: '文本太短,至少 50 个字' }), { status: 400 }); } if (text.length > 10000) { return new Response(JSON.stringify({ error: '文本太长,最多 10000 个字' }), { status: 400 }); } // 调用 OpenAI 流式接口 const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, }, body: JSON.stringify({ model: 'gpt-4o-mini', stream: true, temperature: 0.3, // 摘要场景用低温度,输出更稳定 messages: [ { role: 'system', content: '你是一个专业的文本摘要助手。请用三句话概括用户提供的文章核心内容。要求:第一句点明主题,第二句总结关键论点,第三句给出结论或启发。语言精炼,不要套话。', }, { role: 'user', content: `请摘要以下文章:${text}`, }, ], }), }); // 直接透传 SSE 流给前端 return new Response(response.body, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, });}
这里直接将OpenAI的SSE流透传给前端,不做中间缓存和二次封装,数据流向越短,延迟越低。
2.3 前端:流式渲染
// components/SummaryOutput.tsx'use client';import { useState } from 'react';import styles from './SummaryOutput.module.css';interface Props { text: string; onComplete: (summary: string) => void;}export default function SummaryOutput({ text, onComplete }: Props) { const [summary, setSummary] = useState(''); const [loading, setLoading] = useState(false); const handleSummarize = async () => { setLoading(true); setSummary(''); const response = await fetch('/api/summarize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text }), }); if (!response.ok || !response.body) { setSummary('摘要生成失败,请重试'); setLoading(false); return; } // 流式读取 SSE const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let fullText = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 最后一行可能不完整,留到下次 for (const line of lines) { if (line === 'data: [DONE]') continue; if (!line.startsWith('data: ')) continue; try { const json = JSON.parse(line.slice(6)); const content = json.choices.[0].delta.content || ''; fullText += content; setSummary(fullText); // 逐字更新,打字机效果 } catch { // 跳过解析失败的行 } } } setLoading(false); onComplete(fullText); }; return ( {summary && ( {summary}
{!loading && } )} );}
流式读取SSE并逐字更新,打造打字机效果,让用户实时看到AI的“思考输出”。
2.4 一键复制
// components/CopyButton.tsx'use client';import { useState } from 'react';import styles from './CopyButton.module.css';export default function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false); const handleCopy = async () => { try { await na vigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { // 降级方案:用 textarea 选中复制 const textarea = document.createElement('textarea'); textarea.value = text; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; if (!text) return null; return ( );}
复制功能简洁直接:优先使用Clipboard API,降级方案则用textarea选中复制,覆盖所有浏览器场景。
三、样式:克制的美
3.1 设计原则
整个UI仅使用黑、白、一个强调色,不用渐变,不用阴影堆砌。
/* app/page.module.css */.main { max-width: 640px; margin: 0 auto; padding: 4rem 1.5rem; font-family: -apple-system, 'Noto Sans SC', sans-serif;}.title { font-size: 1.5rem; font-weight: 600; color: #1a1a1a; margin-bottom: 0.5rem;}.subtitle { font-size: 0.875rem; color: #888; margin-bottom: 2rem;}.textarea { width: 100%; min-height: 200px; padding: 1rem; border: 1px solid #e5e5e5; border-radius: 8px; font-size: 0.9375rem; line-height: 1.6; resize: vertical; transition: border-color 0.2s; font-family: inherit;}.textarea:focus { outline: none; border-color: #8b5cf6;}/* 输出区域:打字机闪烁光标 */.output { margin-top: 1.5rem; padding: 1.25rem; background: #fafafa; border-radius: 8px; line-height: 1.8; color: #333;}.cursor { display: inline-block; width: 2px; height: 1em; background: #8b5cf6; animation: blink 0.8s infinite; vertical-align: text-bottom; margin-left: 2px;}@keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; }}
一个紫色作为强调色,一个闪烁光标模拟打字机效果,这正是“克制的美”。
四、部署:三条命令上线
# 第一步:环境变量echo "OPENAI_API_KEY=sk-你的密钥" > .env.local# 第二步:推到 GitHubgit add . && git commit -m "feat: 极简摘要工具" && git push# 第三步:在 Vercel 导入项目# 打开 vercel.com -> Import -> 选择仓库 -> 填入环境变量 -> Deploy# 完事。
Vercel会自动检测Next.js项目,零配置部署。自带HTTPS、CDN、Edge Runtime,开箱即用。
五、避坑指南
5.1 流式透传的陷阱
Vercel的Edge Runtime和Node.js Runtime对流式的支持不同:
// 如果用 Edge Runtime(推荐,更快)export const runtime = 'edge';// 如果用 Node.js Runtime,需要用 ReadableStream 手动包一层// Edge Runtime 可以直接透传 response.body
5.2 成本控制
GPT-4o-mini很便宜,但不限制的话仍可能被刷。
// 简单的速率限制(基于 IP)const rateLimitMap = new Map();function checkRateLimit(ip: string): boolean { const now = Date.now(); const windowMs = 60 * 1000; // 1 分钟窗口 const maxRequests = 10; // 每分钟最多 10 次 const timestamps = rateLimitMap.get(ip) || []; const recent = timestamps.filter(t => now - t < windowMs); if (recent.length >= maxRequests) { return false; // 超限 } recent.push(now); rateLimitMap.set(ip, recent); return true;}
5.3 SEO 基础
code>// app/layout.tsxexport const metadata = { title: '三句话摘要 - AI 智能文本摘要工具', description: '粘贴任意长文,AI 帮你用三句话精准概括核心内容。免费、快速、无需注册。',};
六、总结
三天,8个文件,一个能用的AI摘要产品。技术栈越简单,迭代速度越快。不要在Day1就引入Redis、数据库、用户系统。先把核心价值跑通,让用户用起来,再按需添加。好的代码,是删出来的。
