# Stack Research **Domain:** Python content automation backend + React SPA frontend (bulk social media content generation) **Researched:** 2026-03-07 **Confidence:** HIGH (all versions verified against PyPI and npm registries as of March 2026) --- ## Recommended Stack ### Core Technologies | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | Python | 3.12+ | Runtime | LTS, full async support, matches Docker slim images | | FastAPI | 0.135.1 | API framework + static file serving | Native async, Pydantic v2 integration, StaticFiles for SPA, OpenAPI auto-docs. Current standard for Python REST APIs. | | Uvicorn | 0.41.0 | ASGI server | Production-grade async server for FastAPI. "Standard" install includes uvicorn + extras. | | Pydantic | 2.12.5 | Data validation + settings | FastAPI depends on it. v2 is 5-50x faster than v1. Required for request/response schemas. | | anthropic (SDK) | 0.84.0 | Claude API client | Official Anthropic Python SDK. Supports sync/async, streaming via `client.messages.stream()`. | | React | 19.2.4 | Frontend SPA | Latest stable. New concurrent features, improved hooks. Ecosystem standard. | | Vite | 7.x (latest stable) | Frontend build tool | Faster than Webpack/CRA. Native ES modules in dev. First-party React plugin. Single-command build for production. | | Tailwind CSS | 4.2.1 | Utility CSS | v4 is major rewrite: 5x faster builds, no tailwind.config.js required, native @tailwindcss/vite plugin. | ### Supporting Libraries #### Python Backend | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | python-dotenv | 1.2.2 | Environment variable loading | Load `.env` in local dev. In Docker, use compose `environment:` directly. | | python-multipart | 0.0.22 | Form/file upload parsing | Required by FastAPI for `UploadFile` and `Form` parameters. | | httpx | 0.28.1 | Async HTTP client | Calling Unsplash API. Native async, HTTP/2 support, replaces requests for async code. | | aiofiles | 24.x | Async file I/O | Reading/writing prompt files and generated CSVs without blocking the event loop. | **CSV generation:** Use Python's built-in `csv` module (stdlib). No external dependency needed. This is the correct choice for structured CSV output to Canva. Pandas/Polars are overkill — the project generates structured rows, not analyzes datasets. **Prompt file management:** Use Python's built-in `pathlib.Path` + `json`/`yaml` stdlib. No external library needed for file-based prompt storage. #### React Frontend | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | @tailwindcss/vite | 4.x | Tailwind v4 Vite plugin | Required for Tailwind v4 integration. Replaces PostCSS config. | | shadcn/ui | latest CLI | UI component collection | Copy-owned components built on Radix UI + Tailwind. Use for forms, tables, modals, progress indicators. Not an npm dep — CLI copies source. | | @radix-ui/react-* | latest | Accessible primitives | Pulled in automatically by shadcn/ui. Headless, accessible. | | react-router-dom | 7.x | Client-side routing | SPA navigation between generation wizard, history, settings pages. | | @tanstack/react-query | 5.x | Server state + async data | Handles API call lifecycle (loading, error, cache). Essential for polling long-running generation jobs. | | lucide-react | latest | Icons | shadcn/ui default icon set. Consistent with component library. | ### Development Tools | Tool | Purpose | Notes | |------|---------|-------| | Docker | Container runtime | Multi-stage build: Node stage builds React, Python stage serves everything | | docker compose | Local dev + VPS deploy | Single `docker-compose.yml` for both environments with env overrides | | Vite dev proxy | Dev CORS bypass | `vite.config.ts` proxy `/api` to `localhost:8000` during development | | TypeScript | Frontend type safety | Enabled by default in Vite React template. Catches API contract errors at compile time. | | ESLint + prettier | Code quality | Vite template includes eslint. Add prettier for formatting. | --- ## Installation ### Backend ```bash # Create and activate venv python -m venv .venv source .venv/bin/activate # Linux/Mac # .venv\Scripts\activate # Windows # Core pip install fastapi[standard]==0.135.1 pip install anthropic==0.84.0 pip install httpx==0.28.1 pip install python-dotenv==1.2.2 pip install aiofiles # python-multipart is included in fastapi[standard] # pydantic is included in fastapi[standard] # uvicorn is included in fastapi[standard] # Generate requirements.txt pip freeze > requirements.txt ``` **Note:** `fastapi[standard]` installs FastAPI + uvicorn[standard] + python-multipart + pydantic + email-validator. This is the recommended install per FastAPI docs as of v0.100+. Do NOT install `fastapi-slim` — it was deprecated. ### Frontend ```bash # Create Vite + React + TypeScript project npm create vite@latest frontend -- --template react-ts cd frontend # Install Tailwind v4 with Vite plugin npm install tailwindcss @tailwindcss/vite # Install routing and data fetching npm install react-router-dom @tanstack/react-query # Install shadcn/ui (interactive CLI) npx shadcn@latest init # Add components as needed npx shadcn@latest add button input table progress select # Dev dependencies npm install -D typescript @types/react @types/react-dom ``` ### Tailwind v4 Configuration In `vite.config.ts`: ```typescript import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [ react(), tailwindcss(), ], }) ``` In `src/index.css` (no `tailwind.config.js` needed in v4): ```css @import "tailwindcss"; ``` --- ## Architecture: Single-Container Deploy FastAPI serves both the API and the built React SPA. This eliminates CORS configuration and simplifies Docker deployment on the VPS. **Pattern:** ```python # main.py from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse import os app = FastAPI() # API routes first app.include_router(api_router, prefix="/api") # SPA catch-all: serve index.html for any non-API route @app.get("/{full_path:path}") async def serve_spa(full_path: str): static_dir = "static" file_path = os.path.join(static_dir, full_path) if os.path.isfile(file_path): return FileResponse(file_path) return FileResponse(os.path.join(static_dir, "index.html")) # Mount static files (CSS, JS, assets) app.mount("/assets", StaticFiles(directory="static/assets"), name="assets") ``` **Multi-stage Dockerfile:** ```dockerfile # Stage 1: Build React frontend FROM node:22-slim AS frontend-builder WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm ci COPY frontend/ . RUN npm run build # Stage 2: Python runtime FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY backend/ . # Copy built frontend into FastAPI static directory COPY --from=frontend-builder /app/frontend/dist ./static CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"] ``` --- ## Claude API Integration **Recommended model:** `claude-sonnet-4-5` for content generation (best cost/quality ratio for structured text output at volume). **Sync pattern** (for bulk generation — simpler, no streaming needed for batch jobs): ```python import anthropic client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from env response = client.messages.create( model="claude-sonnet-4-5", max_tokens=2000, messages=[ {"role": "user", "content": prompt} ] ) content = response.content[0].text ``` **Streaming pattern** (for real-time UI feedback during generation): ```python with client.messages.stream( model="claude-sonnet-4-5", max_tokens=2000, messages=[{"role": "user", "content": prompt}] ) as stream: for text in stream.text_stream: yield text # SSE to frontend ``` **Async pattern** (for FastAPI async endpoints): ```python async_client = anthropic.AsyncAnthropic() async def generate_content(prompt: str) -> str: response = await async_client.messages.create( model="claude-sonnet-4-5", max_tokens=2000, messages=[{"role": "user", "content": prompt}] ) return response.content[0].text ``` --- ## Unsplash API Integration **Rate limits:** - Demo (free) tier: **50 requests/hour** - Production (after manual approval): 5,000 requests/hour **Implication for bulk generation:** With 50 req/hour limit and bulk carousel generation, implement: 1. Local image cache (save URLs/download images locally after first fetch) 2. Deduplication (don't re-fetch same search term if cached) 3. Fallback to cached results when limit approached **Python integration via httpx** (no dedicated library needed — Unsplash REST API is simple): ```python import httpx async def search_unsplash(query: str, count: int = 1) -> list[dict]: async with httpx.AsyncClient() as client: response = await client.get( "https://api.unsplash.com/search/photos", params={"query": query, "per_page": count}, headers={"Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}"} ) response.raise_for_status() return response.json()["results"] ``` **Why not python-unsplash library:** `python-unsplash` (v1.2.5, last updated Nov 2023) and `pyunsplash` (v2020) are both poorly maintained. The Unsplash REST API is simple enough that `httpx` direct calls are cleaner, more maintainable, and don't add a stale dependency. --- ## CSV Generation for Canva Bulk Create Use Python's stdlib `csv` module — no additional dependency: ```python import csv import io def generate_canva_csv(rows: list[dict], fields: list[str]) -> str: """Generate CSV string formatted for Canva Bulk Create.""" output = io.StringIO() writer = csv.DictWriter(output, fieldnames=fields) writer.writeheader() writer.writerows(rows) return output.getvalue() ``` **FastAPI endpoint for CSV download:** ```python from fastapi.responses import StreamingResponse @app.get("/api/export/{job_id}/csv") async def export_csv(job_id: str): csv_content = get_job_csv(job_id) # retrieve generated CSV return StreamingResponse( io.StringIO(csv_content), media_type="text/csv", headers={"Content-Disposition": f"attachment; filename=carousels_{job_id}.csv"} ) ``` --- ## File-Based Prompt Management No database. Prompt templates stored as files: ``` backend/ prompts/ frameworks/ persuasion_nurturing.txt schwartz_awareness.txt niches/ tech_saas.txt finance.txt system_prompt.txt ``` **Access pattern:** ```python from pathlib import Path PROMPTS_DIR = Path(__file__).parent / "prompts" def load_prompt(category: str, name: str) -> str: prompt_path = PROMPTS_DIR / category / f"{name}.txt" return prompt_path.read_text(encoding="utf-8") def list_prompts(category: str) -> list[str]: return [p.stem for p in (PROMPTS_DIR / category).glob("*.txt")] ``` **Advantages over DB:** Zero setup, version-controlled with git, easy to edit, diff-friendly. Appropriate for this scale (tens of prompt files, not millions of records). --- ## Alternatives Considered | Recommended | Alternative | When to Use Alternative | |-------------|-------------|-------------------------| | FastAPI | Django REST Framework | When you need Django ORM, admin panel, or large team with Django expertise | | FastAPI | Flask | Flask is simpler but lacks native async, auto OpenAPI docs, and Pydantic integration | | React + Vite | Next.js | When you need SSR, SEO, or ISR. Overkill for an internal B2B tool served on a subpath. | | Tailwind v4 | Tailwind v3 | Only if targeting environments where v4's new CSS syntax causes issues. v4 is stable as of 2025. | | shadcn/ui | MUI / Chakra UI | When you need a full pre-styled design system without customization. shadcn is better for owned components. | | stdlib csv | pandas | When doing data analysis, not just writing structured rows. pandas adds ~30MB to container. | | httpx direct | python-unsplash | Only if Unsplash API complexity grows significantly (pagination, upload, etc.) | | Single container | Separate nginx + Python | When frontend and backend scale independently, or need different update cadences. Overkill for this project. | --- ## What NOT to Use | Avoid | Why | Use Instead | |-------|-----|-------------| | `fastapi-slim` | Deprecated as of recent FastAPI versions. Missing extras. | `fastapi[standard]` | | `create-react-app` | Unmaintained since 2023. No Vite, slow builds, outdated webpack. | `npm create vite@latest` | | `requests` library | Sync-only. Blocks FastAPI async event loop when called from async endpoints. | `httpx` with `AsyncClient` | | `openai` library for Claude | Wrong SDK. Anthropic has official `anthropic` SDK with better type safety and streaming helpers. | `anthropic` SDK | | Tailwind v3 PostCSS config | v3 config is still valid but v4 drops `tailwind.config.js` requirement and is significantly faster. | Tailwind v4 + `@tailwindcss/vite` | | `python-unsplash` / `pyunsplash` | Both stale (2020-2023), no async support, thin wrappers adding maintenance risk. | `httpx` direct calls | | `uvicorn` without `[standard]` | Base uvicorn lacks websocket support and other extensions. | `uvicorn[standard]` (included in `fastapi[standard]`) | | Separate nginx container | Adds operational complexity without benefit for single-service subpath deploy. | FastAPI serves static files directly. | --- ## Stack Patterns by Variant **For development (local):** - Run FastAPI with `uvicorn main:app --reload` on port 8000 - Run Vite dev server on port 5173 with proxy: `vite.config.ts` → `proxy: { '/api': 'http://localhost:8000' }` - No Docker needed locally **For VPS subpath deployment (`lab.mlhub.it/postgenerator/`):** - React must be built with `base: '/postgenerator/'` in `vite.config.ts` - FastAPI must handle requests prefixed with `/postgenerator/` (via reverse proxy or app root_path) - Set `root_path="/postgenerator"` in uvicorn or via `--root-path /postgenerator` flag - lab-router nginx handles the subpath routing **For bulk generation (many carousels in one job):** - Use FastAPI `BackgroundTasks` for async job processing - Store job state in a JSON file (file-based, no DB) - Frontend polls `/api/jobs/{id}/status` with react-query - Streaming SSE optional for real-time token display --- ## Version Compatibility | Package | Compatible With | Notes | |---------|-----------------|-------| | fastapi==0.135.1 | pydantic>=2.0, starlette>=0.46.0 | v0.100+ requires pydantic v2. pydantic v1 not supported. | | anthropic==0.84.0 | Python>=3.8 | Fully supports async via `AsyncAnthropic()`. | | tailwindcss==4.2.1 | @tailwindcss/vite==4.x | v4 requires the Vite plugin, NOT PostCSS plugin (different from v3). | | react==19.2.4 | react-router-dom>=7, @tanstack/react-query>=5 | React 19 has breaking changes from 18. Verify shadcn/ui component compatibility. | | shadcn/ui | react>=18, tailwindcss>=3 | shadcn/ui officially supports Tailwind v4 since early 2025. Use `npx shadcn@latest init`. | | python-multipart==0.0.22 | Python>=3.10 | Required for FastAPI file uploads. Requires py3.10+. | --- ## Sources - [PyPI - fastapi](https://pypi.org/project/fastapi/) — v0.135.1 verified March 1, 2026 (HIGH confidence) - [PyPI - anthropic](https://pypi.org/project/anthropic/) — v0.84.0 verified February 25, 2026 (HIGH confidence) - [PyPI - uvicorn](https://pypi.org/project/uvicorn/) — v0.41.0 verified February 16, 2026 (HIGH confidence) - [PyPI - pydantic](https://pypi.org/project/pydantic/) — v2.12.5 verified (HIGH confidence) - [PyPI - python-dotenv](https://pypi.org/project/python-dotenv/) — v1.2.2 verified March 1, 2026 (HIGH confidence) - [PyPI - httpx](https://pypi.org/project/httpx/) — v0.28.1 verified December 6, 2024 (HIGH confidence) - [PyPI - python-multipart](https://pypi.org/project/python-multipart/) — v0.0.22 verified January 25, 2026 (HIGH confidence) - [npm - react](https://www.npmjs.com/package/react) — v19.2.4 (HIGH confidence, per WebSearch) - [npm - tailwindcss](https://www.npmjs.com/package/tailwindcss) — v4.2.1 verified ~February 2026 (HIGH confidence) - [npm - vite](https://vite.dev/releases) — v7.x confirmed stable 2026 (MEDIUM confidence — WebSearch shows 7.3.1, v8 beta in progress) - [Anthropic Streaming Docs](https://platform.claude.com/docs/en/api/messages-streaming) — Streaming patterns verified directly (HIGH confidence) - [Unsplash API Docs](https://unsplash.com/documentation) — Rate limits 50 req/hr demo, 5000 prod (HIGH confidence) - [FastAPI Static Files Docs](https://fastapi.tiangolo.com/tutorial/static-files/) — SPA serving pattern (HIGH confidence) - [Tailwind CSS v4 Announcement](https://tailwindcss.com/blog/tailwindcss-v4) — v4 features and migration (HIGH confidence) --- *Stack research for: PostGenerator — Instagram carousel automation (Python + FastAPI + React + Claude API)* *Researched: 2026-03-07*