Files
postgenerator/.planning/research/STACK.md
Michele fe6cd4d516 docs: complete project research
Files:
- STACK.md
- FEATURES.md
- ARCHITECTURE.md
- PITFALLS.md
- SUMMARY.md

Key findings:
- Stack: FastAPI 0.135.1 + React 19 + Vite 7 + Tailwind v4, single-container deploy
- Architecture: FastAPI serves React SPA via catch-all, file-based storage (Docker volume), LLMService with retry/backoff
- Critical pitfall: All 9 pitfalls map to Phase 1 — Italian prompts, Canva field constants, UTF-8 BOM, root_path config, per-item bulk isolation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 14:06:44 +01:00

17 KiB

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)


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

# 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

# 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:

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):

@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:

# 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:

# 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):

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):

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):

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):

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:

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:

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:

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.tsproxy: { '/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


Stack research for: PostGenerator — Instagram carousel automation (Python + FastAPI + React + Claude API) Researched: 2026-03-07