Files
quoter/docs/vercel-thumbnails.md

364 lines
9.1 KiB
Markdown
Raw Normal View History

[0.6.1] - 2025-09-02 ### 🚀 Изменено - Упрощение архитектуры - **Генерация миниатюр**: Полностью удалена из Quoter, теперь управляется Vercel Edge API - **Очистка legacy кода**: Удалены все функции генерации миниатюр и сложность - **Документация**: Сокращена с 17 файлов до 7, следуя принципам KISS/DRY - **Смена фокуса**: Quoter теперь сосредоточен на upload + storage, Vercel обрабатывает миниатюры - **Логирование запросов**: Добавлена аналитика источников для оптимизации CORS whitelist - **Реализация таймаутов**: Добавлены настраиваемые таймауты для S3, Redis и внешних операций - **Упрощенная безопасность**: Удален сложный rate limiting, оставлена только необходимая защита upload ### 📝 Обновлено - Консолидирована документация в практическую структуру: - Основной README.md с быстрым стартом - docs/SETUP.md для конфигурации и развертывания - Упрощенный features.md с фокусом на основную функциональность - Добавлен акцент на Vercel по всему коду и документации ### 🗑️ Удалено - Избыточные файлы документации (api-reference, deployment, development, и т.д.) - Дублирующийся контент в нескольких документах - Излишне детальная документация для простого файлового прокси 💋 **Упрощение**: KISS принцип применен - убрали избыточность, оставили суть.
2025-09-02 14:00:54 +03:00
# Vercel Thumbnail Generation Integration
## 🎯 Overview
**Quoter**: Dead simple file upload/download service. Just raw files.
**Vercel**: Smart thumbnail generation and optimization.
Perfect separation of concerns! 💋
## 🔗 URL Patterns for Vercel
### Quoter File URLs
```
https://quoter.discours.io/image.jpg → Original file
https://quoter.discours.io/document.pdf → Original file
```
### Vercel Thumbnail URLs (SolidJS)
```
https://new.discours.io/api/thumb/300/image.jpg → 300px width
https://new.discours.io/api/thumb/600/image.jpg → 600px width
https://new.discours.io/api/thumb/1200/image.jpg → 1200px width
```
## 🛠️ Vercel Configuration
### 1. SolidJS Start Config (app.config.ts)
```typescript
import { defineConfig } from '@solidjs/start/config';
export default defineConfig({
server: {
preset: 'vercel',
},
vite: {
define: {
'process.env.QUOTER_URL': JSON.stringify('https://quoter.discours.io'),
},
},
});
```
### 2. Thumbnail API Route (/api/thumb/[width]/[...path].ts)
```typescript
import { ImageResponse } from '@vercel/og';
import type { APIRoute } from '@solidjs/start';
export const GET: APIRoute = async ({ params, request }) => {
const width = parseInt(params.width);
const imagePath = params.path.split('/').join('/');
const quoterUrl = `https://quoter.discours.io/${imagePath}`;
// Fetch original from Quoter
const response = await fetch(quoterUrl);
if (!response.ok) {
return new Response('Image not found', { status: 404 });
}
// Generate optimized thumbnail using @vercel/og
return new ImageResponse(
(
<img
src={quoterUrl}
style={{
width: width,
height: 'auto',
objectFit: 'contain',
}}
/>
),
{
width: width,
height: Math.round(width * 0.75), // 4:3 aspect ratio
}
);
};
```
## 📋 File Naming Conventions
### Quoter Storage (No Width Patterns)
```
✅ image.jpg → Clean filename
✅ photo-2024.png → kebab-case
✅ user-avatar.webp → descriptive names
✅ document.pdf → any file type
❌ image_300.jpg → No width patterns needed
❌ photo-thumbnail.jpg → No thumbnail suffix
❌ userAvatar.png → No camelCase
```
### URL Routing Examples
```bash
# Client requests thumbnail
GET /api/thumb/600/image.jpg
# Vercel fetches original from Quoter
GET https://quoter.discours.io/image.jpg
# Vercel generates and caches 600px thumbnail
→ Returns optimized image
```
## 🚀 Benefits of This Architecture
### For Quoter
- **Simple storage**: Just store original files
- **No processing**: Zero thumbnail generation load
- **Fast uploads**: Direct S3 storage without resizing
- **Predictable URLs**: Clean file paths
### For Vercel
- **Edge optimization**: Global CDN caching
- **Dynamic sizing**: Any width on-demand
- **Smart caching**: Automatic cache invalidation
- **Format optimization**: WebP/AVIF when supported
## 🔧 SolidJS Frontend Integration
### 1. Install Dependencies
```bash
npm install @tanstack/solid-query @solidjs/start
```
### 2. Query Client Setup (app.tsx)
```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
import { Router } from '@solidjs/router';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
refetchOnWindowFocus: false,
},
},
});
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Router>
{/* Your app components */}
</Router>
</QueryClientProvider>
);
}
```
### 3. File Upload Hook (hooks/useFileUpload.ts)
```tsx
import { createMutation, useQueryClient } from '@tanstack/solid-query';
interface UploadResponse {
url: string;
filename: string;
}
export function useFileUpload() {
const queryClient = useQueryClient();
return createMutation(() => ({
mutationFn: async (file: File): Promise<UploadResponse> => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('https://quoter.discours.io/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${getAuthToken()}`,
},
body: formData,
});
if (!response.ok) {
throw new Error('Upload failed');
}
return response.json();
},
onSuccess: () => {
// Invalidate user quota query
queryClient.invalidateQueries({ queryKey: ['user'] });
},
}));
}
```
### 4. Image Component with Thumbnails (components/Image.tsx)
```tsx
import { createSignal, Show, Switch, Match } from 'solid-js';
interface ImageProps {
filename: string;
width?: number;
alt: string;
fallback?: boolean;
}
export function Image(props: ImageProps) {
const [loadError, setLoadError] = createSignal(false);
const [loading, setLoading] = createSignal(true);
const thumbnailUrl = () =>
props.width
? `https://new.discours.io/api/thumb/${props.width}/${props.filename}`
: `https://quoter.discours.io/${props.filename}`;
const fallbackUrl = () => `https://quoter.discours.io/${props.filename}`;
return (
<Switch>
<Match when={loading()}>
<div class="bg-gray-200 animate-pulse" style={{ width: `${props.width}px`, height: '200px' }} />
</Match>
<Match when={!loadError()}>
<img
src={thumbnailUrl()}
alt={props.alt}
loading="lazy"
onLoad={() => setLoading(false)}
onError={() => {
setLoading(false);
setLoadError(true);
}}
/>
</Match>
<Match when={loadError() && props.fallback !== false}>
<img
src={fallbackUrl()}
alt={props.alt}
loading="lazy"
onLoad={() => setLoading(false)}
/>
</Match>
</Switch>
);
}
```
### 5. User Quota Component (components/UserQuota.tsx)
```tsx
import { createQuery } from '@tanstack/solid-query';
import { Show, Switch, Match } from 'solid-js';
export function UserQuota() {
const query = createQuery(() => ({
queryKey: ['user'],
queryFn: async () => {
const response = await fetch('https://quoter.discours.io/', {
headers: {
'Authorization': `Bearer ${getAuthToken()}`,
},
});
return response.json();
},
}));
return (
<Switch>
<Match when={query.isLoading}>
<div>Loading quota...</div>
</Match>
<Match when={query.isError}>
<div>Error loading quota</div>
</Match>
<Match when={query.isSuccess}>
<Show when={query.data}>
{(data) => (
<div>
<p>Storage: {data().storage_used_mb}MB / {data().storage_limit_mb}MB</p>
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-blue-600 h-2 rounded-full"
style={{ width: `${(data().storage_used_mb / data().storage_limit_mb) * 100}%` }}
/>
</div>
</div>
)}
</Show>
</Match>
</Switch>
);
}
```
## 🔧 Implementation Steps
1. **Quoter**: Serve raw files only (no patterns)
2. **Vercel**: Create SolidJS API routes for thumbnails
3. **Frontend**: Use TanStack Query for data fetching
4. **CORS**: Configure Quoter to allow Vercel domain
## 📊 Request Flow
```mermaid
sequenceDiagram
participant Client
participant Vercel
participant Quoter
participant S3
Client->>Vercel: GET /api/thumb/600/image.jpg
Vercel->>Quoter: GET /image.jpg (original)
Quoter->>S3: Fetch image.jpg
S3->>Quoter: Return file data
Quoter->>Vercel: Return original image
Vercel->>Vercel: Generate 600px thumbnail
Vercel->>Client: Return optimized thumbnail
Note over Vercel: Cache thumbnail at edge
```
## 🎨 Advanced Vercel Features
### Smart Format Detection
```javascript
// Auto-serve WebP/AVIF when supported
export async function GET(request) {
const accept = request.headers.get('accept');
const supportsWebP = accept?.includes('image/webp');
const supportsAVIF = accept?.includes('image/avif');
return new ImageResponse(
// ... image component
{
format: supportsAVIF ? 'avif' : supportsWebP ? 'webp' : 'jpeg',
}
);
}
```
### Quality Optimization
```javascript
// Different quality for different sizes
const quality = width <= 400 ? 75 : width <= 800 ? 85 : 95;
return new ImageResponse(component, {
width,
height,
quality,
});
```
## 🔗 Integration with CORS
Update Quoter CORS whitelist:
```bash
CORS_DOWNLOAD_ORIGINS=https://discours.io,https://*.discours.io,https://*.vercel.app
```
This allows Vercel Edge Functions to fetch originals from Quoter.
## 📈 Performance Benefits
- **Faster uploads**: No server-side resizing in Quoter
- **Global CDN**: Vercel Edge caches thumbnails worldwide
- **On-demand sizing**: Generate any size when needed
- **Smart caching**: Automatic cache headers and invalidation
- **Format optimization**: Serve modern formats automatically
**Result**: Clean separation of concerns - Quoter handles storage, Vercel handles optimization! 🚀