Skip to content

Commit c90e40e

Browse files
authored
feat(blob): Upgrade blob starter example to client uploads, progress, next 15 (#976)
This commit upgrades the blob starter example (https://blob-starter.vercel.app/): - Use client uploads - Display upload progress instead of just loading dots - Multiple UX fixes like reset button, correct handling of image/* only, allow to upload another image once done - Upgrade all packages - Cleanup package.json - Refactor code for easier reading **Before** https://github.com/user-attachments/assets/ccfc6d4b-773c-41db-9966-d9866ffa9c89 **After** https://github.com/user-attachments/assets/6c00f4f7-f270-41dc-b767-e453433eb76d
1 parent c72c796 commit c90e40e

File tree

9 files changed

+3007
-2749
lines changed

9 files changed

+3007
-2749
lines changed

storage/blob-starter/.eslintrc.json

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"typescript.tsdk": "node_modules/.pnpm/[email protected]/node_modules/typescript/lib",
3-
"typescript.enablePromptUseWorkspaceTsdk": true
2+
"typescript.enablePromptUseWorkspaceTsdk": true,
3+
"typescript.tsdk": "node_modules/typescript/lib"
44
}

storage/blob-starter/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ relatedTemplates:
1515

1616
# Vercel Blob Next.js Starter
1717

18-
Simple Next.js template that uses [Vercel Blob](https://vercel.com/blob)for image uploads
18+
Simple Next.js template that uses [Vercel Blob](https://vercel.com/blob) for image uploads
1919

2020
## Demo
2121

22-
https://blob-starter.vercel.app/
22+
https://blob-starter.vercel.app
2323

2424
## How to Use
2525

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
1-
import { put } from '@vercel/blob'
1+
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client'
22
import { NextResponse } from 'next/server'
3-
import { customAlphabet } from 'nanoid'
43

5-
export const runtime = 'edge'
4+
export async function POST(request: Request): Promise<NextResponse> {
5+
const body = (await request.json()) as HandleUploadBody
66

7-
const nanoid = customAlphabet(
8-
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
9-
7
10-
) // 7-character random string
11-
export async function POST(req: Request) {
12-
const file = req.body || ''
13-
const contentType = req.headers.get('content-type') || 'text/plain'
14-
const filename = `${nanoid()}.${contentType.split('/')[1]}`
15-
const blob = await put(filename, file, {
16-
contentType,
17-
access: 'public',
18-
})
7+
try {
8+
const jsonResponse = await handleUpload({
9+
body,
10+
request,
11+
onBeforeGenerateToken: async () =>
12+
// pathname
13+
// clientPayload
14+
{
15+
// Generate a client token for the browser to upload the file
16+
// ⚠️ Authenticate and authorize users before generating the token.
17+
// Otherwise, you're allowing anonymous uploads.
18+
return {
19+
allowedContentTypes: ['image/*'],
20+
maximumSizeInBytes: 50 * 1024 * 1024, // 50MB
21+
}
22+
},
23+
onUploadCompleted: async ({ blob, tokenPayload }) => {
24+
// Get notified of client upload completion
25+
// ⚠️ This will not work during development (localhost),
26+
// Unless you use ngrok or a similar service to expose and test your local server
27+
console.log('blob upload completed', blob, tokenPayload)
28+
},
29+
})
1930

20-
return NextResponse.json(blob)
31+
return NextResponse.json(jsonResponse)
32+
} catch (error) {
33+
return NextResponse.json(
34+
{ error: (error as Error).message },
35+
{ status: 400 } // The webhook will retry 5 times waiting for a 200
36+
)
37+
}
2138
}

storage/blob-starter/components/loading-dots.tsx

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default function ProgressBar({ value }: { value: number }) {
2+
return (
3+
<div className="relative pt-1">
4+
<div className="flex h-2 mb-4 overflow-hidden text-xs bg-blue-200 rounded">
5+
<div
6+
style={{ width: `${value}%` }}
7+
className="flex flex-col justify-center text-center text-white bg-blue-500 shadow-none whitespace-nowrap transition-all duration-500 ease-in-out"
8+
/>
9+
</div>
10+
</div>
11+
)
12+
}

0 commit comments

Comments
 (0)