Framework Integrations

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

app/lib/upload.ts
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

app/routes/api.upload.$.ts
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

app/routes/api.upload.$.ts
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

app/routes/api.upload.$.ts
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

app/lib/upload.ts
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

app/routes/api.upload.$.ts
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

app/components/UploadDemo.tsx
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

app/routes/upload.tsx
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

app/routes/__root.tsx
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

app.config.ts
import { defineConfig } from '@tanstack/start/config';

export default defineConfig({
  server: {
    preset: 'vercel'
  }
});

Netlify Deployment

app.config.ts
import { defineConfig } from '@tanstack/start/config';

export default defineConfig({
  server: {
    preset: 'netlify'
  }
});

Docker Deployment

Dockerfile
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.