SvelteKit
Fast, modern file uploads with SvelteKit using Web Standards - no adapter needed!
🚧 Client-Side In Development: SvelteKit server-side integration is fully functional with Web Standards APIs. However, SvelteKit-specific client-side components and hooks are still in development. You can use the standard pushduck client APIs for now.
Using pushduck with SvelteKit
SvelteKit is the official application framework for Svelte. It uses Web Standards APIs and provides excellent performance with minimal JavaScript. Since SvelteKit uses standard Request
/Response
objects, pushduck handlers work directly without any adapters!
Web Standards Native: SvelteKit server endpoints use Web Standard Request
/Response
objects, making pushduck integration seamless with zero overhead.
Quick Setup
Install dependencies
npm install pushduck
yarn add pushduck
pnpm add pushduck
bun add pushduck
Configure upload router
import { createUploadConfig } from 'pushduck/server';
const { s3, createS3Router } = createUploadConfig()
.provider("cloudflareR2",{
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
region: 'auto',
endpoint: process.env.AWS_ENDPOINT_URL!,
bucket: process.env.S3_BUCKET_NAME!,
accountId: process.env.R2_ACCOUNT_ID!,
})
.build();
export const uploadRouter = createS3Router({
imageUpload: s3.image().maxFileSize("5MB"),
documentUpload: s3.file().maxFileSize("10MB")
});
export type AppUploadRouter = typeof uploadRouter;
Create API route
import type { RequestHandler } from './$types';
import { uploadRouter } from '$lib/upload';
// Direct usage - no adapter needed!
export const GET: RequestHandler = async ({ request }) => {
return uploadRouter.handlers(request);
};
export const POST: RequestHandler = async ({ request }) => {
return uploadRouter.handlers(request);
};
Basic Integration
Simple Upload Route
import type { RequestHandler } from './$types';
import { uploadRouter } from '$lib/upload';
// Method 1: Combined handler (recommended)
export const GET: RequestHandler = async ({ request }) => {
return uploadRouter.handlers(request);
};
export const POST: RequestHandler = async ({ request }) => {
return uploadRouter.handlers(request);
};
// Method 2: Separate handlers (if you need method-specific logic)
// export const { GET, POST } = uploadRouter.handlers;
With SvelteKit Hooks
import type { Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
const corsHandler: Handle = async ({ event, resolve }) => {
if (event.url.pathname.startsWith('/api/upload')) {
if (event.request.method === 'OPTIONS') {
return new Response(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
}
const response = await resolve(event);
if (event.url.pathname.startsWith('/api/upload')) {
response.headers.set('Access-Control-Allow-Origin', '*');
}
return response;
};
export const handle = sequence(corsHandler);
Advanced Configuration
Authentication with SvelteKit
import { createUploadConfig } from 'pushduck/server';
import { getUserFromSession } from '$lib/auth';
const { s3, createS3Router } = createUploadConfig()
.provider("cloudflareR2",{
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
region: 'auto',
endpoint: process.env.AWS_ENDPOINT_URL!,
bucket: process.env.S3_BUCKET_NAME!,
accountId: process.env.R2_ACCOUNT_ID!,
})
.paths({
prefix: 'uploads',
generateKey: (file, metadata) => {
return `${metadata.userId}/${Date.now()}/${file.name}`;
}
})
.build();
export const uploadRouter = createS3Router({
// Private uploads with session authentication
privateUpload: s3
.image()
.maxFileSize("5MB")
.middleware(async ({ req }) => {
const cookies = req.headers.get('Cookie');
const sessionId = parseCookie(cookies)?.sessionId;
if (!sessionId) {
throw new Error('Authentication required');
}
const user = await getUserFromSession(sessionId);
if (!user) {
throw new Error('Invalid session');
}
return {
userId: user.id,
username: user.username,
};
}),
// Public uploads (no auth)
publicUpload: s3
.image()
.maxFileSize("2MB")
// No middleware = public access
});
export type AppUploadRouter = typeof uploadRouter;
Client-Side Usage
Upload Component
<script lang="ts">
import { useUpload } from "pushduck/client";
import type { AppUploadRouter } from "$lib/upload";
const { UploadButton, UploadDropzone } = useUpload<AppUploadRouter>({
endpoint: "/api/upload",
});
function handleUploadComplete(files: any[]) {
console.log("Files uploaded:", files);
alert("Upload completed!");
}
function handleUploadError(error: Error) {
console.error("Upload error:", error);
alert(`Upload failed: ${error.message}`);
}
</script>
<div class="space-y-6">
<div>
<h3 class="text-lg font-semibold mb-2">Image Upload</h3>
<UploadButton
endpoint="imageUpload"
onClientUploadComplete={handleUploadComplete}
onUploadError={handleUploadError}
/>
</div>
<div>
<h3 class="text-lg font-semibold mb-2">Document Upload</h3>
<UploadDropzone
endpoint="documentUpload"
onClientUploadComplete={handleUploadComplete}
onUploadError={handleUploadError}
/>
</div>
</div>
Using in Pages
<script lang="ts">
import FileUpload from "$lib/components/FileUpload.svelte";
</script>
<svelte:head>
<title>File Upload Demo</title>
</svelte:head>
<main class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">File Upload Demo</h1>
<FileUpload />
</main>
File Management
Server-Side File Listing
import type { PageServerLoad } from './$types';
import { db } from '$lib/database';
export const load: PageServerLoad = async ({ locals }) => {
const files = await db.file.findMany({
where: { userId: locals.user?.id },
orderBy: { createdAt: 'desc' },
});
return {
files: files.map(file => ({
id: file.id,
name: file.name,
url: file.url,
size: file.size,
uploadedAt: file.createdAt,
})),
};
};
<script lang="ts">
import type { PageData } from './$types';
import FileUpload from '$lib/components/FileUpload.svelte';
export let data: PageData;
function formatFileSize(bytes: number): string {
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
</script>
<svelte:head>
<title>My Files</title>
</svelte:head>
<main class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">My Files</h1>
<div class="mb-8">
<FileUpload />
</div>
<div>
<h2 class="text-2xl font-semibold mb-4">Uploaded Files</h2>
{#if data.files.length === 0}
<p class="text-gray-500">No files uploaded yet.</p>
{:else}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#each data.files as file}
<div class="border rounded-lg p-4 hover:shadow-md transition-shadow">
<h3 class="font-medium truncate" title={file.name}>
{file.name}
</h3>
<p class="text-sm text-gray-500">
{formatFileSize(file.size)}
</p>
<p class="text-sm text-gray-500">
{new Date(file.uploadedAt).toLocaleDateString()}
</p>
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
class="text-blue-500 hover:underline text-sm mt-2 inline-block"
>
View File
</a>
</div>
{/each}
</div>
{/if}
</div>
</main>
Deployment Options
import adapter from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
runtime: 'nodejs18.x',
regions: ['iad1'],
}),
}
};
export default config;
import adapter from '@sveltejs/adapter-netlify';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
edge: false,
split: false
}),
}
};
export default config;
import adapter from '@sveltejs/adapter-node';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
out: 'build'
}),
}
};
export default config;
import adapter from '@sveltejs/adapter-cloudflare';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
routes: {
include: ['/*'],
exclude: ['<build>']
}
}),
}
};
export default config;
Environment Variables
# AWS Configuration
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_S3_BUCKET=your-bucket-name
# SvelteKit
PUBLIC_UPLOAD_ENDPOINT=http://localhost:5173/api/upload
Performance Benefits
Minimal JavaScript
Svelte compiles to vanilla JS with small bundle sizes
Web Standards
No adapter overhead - direct Request/Response usage
Fast Hydration
Selective hydration of interactive components
Edge Ready
Works on edge runtimes and CDNs
Real-Time Upload Progress
<script lang="ts">
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
const uploadProgress = writable(0);
const isUploading = writable(false);
let fileInput: HTMLInputElement;
async function handleFileUpload(event: Event) {
const target = event.target as HTMLInputElement;
const files = target.files;
if (!files || files.length === 0) return;
isUploading.set(true);
uploadProgress.set(0);
try {
// Simulate upload progress
for (let i = 0; i <= 100; i += 10) {
uploadProgress.set(i);
await new Promise(resolve => setTimeout(resolve, 100));
}
alert('Upload completed!');
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed!');
} finally {
isUploading.set(false);
uploadProgress.set(0);
}
}
</script>
<div class="upload-container">
<input
bind:this={fileInput}
type="file"
multiple
on:change={handleFileUpload}
disabled={$isUploading}
class="file-input"
/>
{#if $isUploading}
<div class="progress-container">
<div class="progress-bar" style="width: {$uploadProgress}%"></div>
</div>
<p class="progress-text">{$uploadProgress}% uploaded</p>
{/if}
</div>
<style>
.upload-container {
max-width: 400px;
margin: 0 auto;
}
.file-input {
width: 100%;
padding: 12px;
border: 2px dashed #ccc;
border-radius: 8px;
cursor: pointer;
}
.file-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.progress-container {
width: 100%;
height: 8px;
background-color: #f0f0f0;
border-radius: 4px;
margin-top: 12px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #4CAF50;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
margin-top: 8px;
font-size: 14px;
color: #666;
}
</style>
Troubleshooting
Common Issues
- Route not found: Ensure your route is
src/routes/api/upload/[...path]/+server.ts
- Build errors: Check that pushduck is properly installed
- CORS issues: SvelteKit handles CORS automatically for same-origin requests
Debug Mode
Enable debug logging:
export const uploadRouter = createS3Router({
// ... routes
}).middleware(async ({ req, file }) => {
if (import.meta.env.DEV) {
console.log("Upload request:", req.url);
console.log("File:", file.name, file.size);
}
return {};
});
SvelteKit provides an excellent foundation for building fast, modern web applications with pushduck, combining the power of Svelte's reactive framework with Web Standards APIs.