求NextJS 13.3基于App Router的本地文件上传最简示例
NextJS 13.3 本地文件上传最简实现
以下是基于NextJS 13.3+ App Router特性的完整可运行方案,包含前端上传表单和后端API路由处理:
1. 前端上传表单(客户端组件)
创建 app/upload/page.tsx,标记为客户端组件以处理交互逻辑:
'use client'; import { useState } from 'react'; export default function UploadPage() { const [file, setFile] = useState<File | null>(null); const [uploadStatus, setUploadStatus] = useState<string | null>(null); const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files?.[0]) { setFile(e.target.files[0]); } }; const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); if (!file) return; setUploadStatus('上传中...'); const formData = new FormData(); formData.append('file', file); try { const res = await fetch('/api/upload', { method: 'POST', body: formData, }); if (res.ok) { const data = await res.json(); setUploadStatus(`上传成功!文件可访问路径:${data.filePath}`); setFile(null); } else { setUploadStatus('上传失败,请重试'); } } catch (err) { setUploadStatus('上传出错:' + (err as Error).message); } }; return ( <div style={{ maxWidth: 600, margin: '2rem auto', padding: '0 1rem' }}> <h1>文件上传</h1> <form onSubmit={handleSubmit} encType="multipart/form-data"> <div style={{ marginBottom: '1rem' }}> <input type="file" onChange={handleFileChange} required style={{ padding: '0.5rem' }} /> </div> <button type="submit" disabled={!file} style={{ padding: '0.5rem 1rem', backgroundColor: '#0070f3', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', }} > 上传文件 </button> </form> {uploadStatus && <p style={{ marginTop: '1rem' }}>{uploadStatus}</p>} </div> ); }
2. 后端API路由处理
创建 app/api/upload/route.ts,用于接收并存储文件:
首先安装依赖:
npm install formidable @types/formidable
路由代码:
import { NextResponse } from 'next/server'; import formidable from 'formidable'; import fs from 'fs/promises'; import path from 'path'; // 禁用NextJS默认的body解析,交由formidable处理 export const config = { api: { bodyParser: false, }, }; // 指定使用Node.js运行时(Edge Runtime不支持文件系统操作) export const runtime = 'nodejs'; export async function POST(request: Request) { const form = formidable({ uploadDir: path.join(process.cwd(), 'public/uploads'), keepExtensions: true, // 保留原始文件扩展名 }); try { // 自动创建uploads目录(不存在时) await fs.mkdir(path.join(process.cwd(), 'public/uploads'), { recursive: true }); // 解析表单数据 const { files } = await new Promise<{ fields: formidable.Fields; files: formidable.Files }>((resolve, reject) => { form.parse(request, (err, fields, files) => { if (err) reject(err); else resolve({ fields, files }); }); }); const uploadedFile = files.file as formidable.File; if (!uploadedFile) { return NextResponse.json({ error: '未选择文件' }, { status: 400 }); } // 返回前端可直接访问的文件路径 const filePath = `/uploads/${path.basename(uploadedFile.filepath)}`; return NextResponse.json({ success: true, filePath }); } catch (err) { return NextResponse.json({ error: (err as Error).message }, { status: 500 }); } }
3. 关键配置与注意事项
- 文件会存储在项目根目录的
public/uploads文件夹,可通过http://localhost:3000/uploads/[文件名]直接访问 - 可在formidable配置中添加限制规则:
formidable({ uploadDir: path.join(process.cwd(), 'public/uploads'), keepExtensions: true, maxFileSize: 5 * 1024 * 1024, // 限制最大5MB filter: (part) => part.mimetype?.startsWith('image/') || part.mimetype?.startsWith('application/pdf'), // 限制文件类型 }); - 生产环境建议使用云存储(如AWS S3)替代本地存储,Vercel等平台不支持持久化本地文件
- 若需调整最大请求大小,修改
next.config.js:/** @type {import('next').NextConfig} */ const nextConfig = { serverRuntimeConfig: { maxRequestBodySize: '5mb', }, }; module.exports = nextConfig;
内容的提问来源于stack exchange,提问作者ArnasOFC
相关产品推荐
相关产品推荐

