TanStack Start
Full-stack React framework with Web Standards support - no adapter needed!
Using pushduck with TanStack Start
TanStack Start is a full-stack React framework with built-in Web Standards support. Since TanStack Start provides event.request as a Web Standard Request object, pushduck handlers work directly without any adapters!
Web Standards Native: TanStack Start's API handlers receive { request } as a Web Standard Request object, making pushduck integration seamless with zero overhead.
Quick Setup
Install dependencies
npm install pushduckyarn add pushduckpnpm add pushduckbun add pushduckConfigure 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 { createAPIFileRoute } from '@tanstack/start/api';
import { uploadRouter } from '../lib/upload';
export const Route = createAPIFileRoute('/api/upload/$')({
GET: ({ request }) => uploadRouter.handlers(request),
POST: ({ request }) => uploadRouter.handlers(request),
});Basic Integration
API Route Handler
import { createAPIFileRoute } from '@tanstack/start/api';
import { uploadRouter } from '../lib/upload';
export const Route = createAPIFileRoute('/api/upload/$')({
// Method 1: Individual handlers
GET: ({ request }) => uploadRouter.handlers.GET(request),
POST: ({ request }) => uploadRouter.handlers.POST(request),
// Method 2: Universal handler (alternative approach)
// You could also create a single handler that delegates:
// handler: ({ request }) => uploadRouter.handlers(request)
});With Middleware
import { createAPIFileRoute } from '@tanstack/start/api';
import { uploadRouter } from '../lib/upload';
// Simple CORS middleware
function withCORS(handler: (ctx: any) => Promise<Response>) {
return async (ctx: any) => {
const response = await handler(ctx);
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return response;
};
}
export const Route = createAPIFileRoute('/api/upload/$')({
GET: withCORS(({ request }) => uploadRouter.handlers.GET(request)),
POST: withCORS(({ request }) => uploadRouter.handlers.POST(request)),
OPTIONS: () => new Response(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
}),
});Advanced Configuration
Authentication with TanStack Start
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!,
})
.paths({
prefix: 'uploads',
generateKey: (file, metadata) => {
return `${metadata.userId}/${Date.now()}/${file.name}`;
}
})
.build();
export const uploadRouter = createS3Router({
// Private uploads with authentication
privateUpload: s3
.image()
.maxFileSize("5MB")
.middleware(async ({ req }) => {
const authHeader = req.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
throw new Error('Authorization required');
}
const token = authHeader.substring(7);
try {
const user = await verifyJWT(token);
return {
userId: user.id,
userRole: user.role
};
} catch (error) {
throw new Error('Invalid token');
}
}),
// Public uploads (no auth)
publicUpload: s3
.image()
.maxFileSize("2MB")
// No middleware = public access
});
async function verifyJWT(token: string) {
// Your JWT verification logic here
return { id: 'user-123', role: 'user' };
}
export type AppUploadRouter = typeof uploadRouter;Protected API Routes
import { createAPIFileRoute } from '@tanstack/start/api';
import { uploadRouter } from '../lib/upload';
// Authentication middleware
async function requireAuth(request: Request) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
throw new Response(JSON.stringify({ error: 'Authorization required' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
const token = authHeader.substring(7);
// Verify token logic here
return { userId: 'user-123' };
}
export const Route = createAPIFileRoute('/api/upload/$')({
GET: async ({ request }) => {
await requireAuth(request);
return uploadRouter.handlers.GET(request);
},
POST: async ({ request }) => {
await requireAuth(request);
return uploadRouter.handlers.POST(request);
},
});Client-Side Integration
Upload Component
import { useState } from 'react';
import { createUploadClient } from 'pushduck/client';
import type { AppUploadRouter } from '../lib/upload';
const uploadClient = createUploadClient<AppUploadRouter>({
baseUrl: process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: 'https://your-domain.com'
});
export function UploadDemo() {
const [uploading, setUploading] = useState(false);
const [results, setResults] = useState<any[]>([]);
const handleUpload = async (files: File[]) => {
if (!files.length) return;
setUploading(true);
try {
const uploadResults = await uploadClient.upload('imageUpload', {
files,
metadata: { userId: 'demo-user' }
});
setResults(uploadResults);
console.log('Upload successful:', uploadResults);
} catch (error) {
console.error('Upload failed:', error);
} finally {
setUploading(false);
}
};
return (
<div className="space-y-4">
<div>
<input
type="file"
multiple
accept="image/*"
onChange={(e) => {
if (e.target.files) {
handleUpload(Array.from(e.target.files));
}
}}
disabled={uploading}
className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
/>
</div>
{uploading && (
<div className="text-blue-600">Uploading...</div>
)}
{results.length > 0 && (
<div className="space-y-2">
<h3 className="font-medium">Upload Results:</h3>
{results.map((result, index) => (
<div key={index} className="p-2 bg-gray-50 rounded">
<div className="text-sm">
<strong>File:</strong> {result.name}
</div>
<div className="text-sm">
<strong>Public URL:</strong>
<a href={result.url} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline ml-1">
{result.url}
</a>
</div>
{result.presignedUrl && (
<div className="text-sm">
<strong>Download URL:</strong>
<a href={result.presignedUrl} target="_blank" rel="noopener noreferrer" className="text-green-600 hover:underline ml-1">
{result.presignedUrl}
</a>
<span className="text-xs text-gray-500 ml-2">(expires in 1 hour)</span>
</div>
)}
</div>
))}
</div>
)}
</div>
);
}Page Integration
import { createFileRoute } from '@tanstack/start';
import { UploadDemo } from '../components/UploadDemo';
export const Route = createFileRoute('/upload')({
component: UploadPage,
});
function UploadPage() {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">File Upload Demo</h1>
<div className="max-w-md mx-auto">
<UploadDemo />
</div>
</div>
);
}Full Application Example
Project Structure
app/
├── components/
│ └── UploadDemo.tsx
├── lib/
│ └── upload.ts
├── routes/
│ ├── api.upload.$.ts
│ ├── upload.tsx
│ └── __root.tsx
├── router.ts
└── main.tsxRoot Layout
import { createRootRoute, Outlet } from '@tanstack/start';
export const Route = createRootRoute({
component: RootComponent,
});
function RootComponent() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TanStack Start + Pushduck</title>
</head>
<body>
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow">
<div className="container mx-auto px-4 py-4">
<h1 className="text-xl font-semibold">Upload Demo</h1>
</div>
</nav>
<main>
<Outlet />
</main>
</div>
</body>
</html>
);
}Performance Benefits
🚀 Zero Overhead
Direct Web Standards integration
No adapter layer means zero performance overhead - pushduck handlers run directly in TanStack Start.
⚡ React Server Components
Modern React architecture
Built on the latest React features with streaming and concurrent rendering.
📦 Full-Stack TypeScript
End-to-end type safety
Complete type safety from server to client with shared types.
🔧 File-Based Routing
Intuitive API structure
Organized API routes with TanStack Start's file-based routing system.
Deployment
Vercel Deployment
import { defineConfig } from '@tanstack/start/config';
export default defineConfig({
server: {
preset: 'vercel'
}
});Netlify Deployment
import { defineConfig } from '@tanstack/start/config';
export default defineConfig({
server: {
preset: 'netlify'
}
});Docker Deployment
FROM node:18-alpine as base
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the app
RUN npm run build
# Expose port
EXPOSE 3000
# Start the app
CMD ["npm", "start"]Modern React + Pushduck: TanStack Start's cutting-edge React architecture combined with pushduck's universal design creates a powerful, type-safe file upload solution.