TanStack Start
Full-stack React framework with Web Standards support - no adapter needed!
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 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().max("5MB"),
documentUpload: s3.file().max("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()
.max("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()
.max("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>URL:</strong>
<a href={result.url} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline ml-1">
{result.url}
</a>
</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.tsx
Root 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.