🏆 Winner – Google DeepMind Nano Banana Hackathon (Kaggle), selected from 832 submissions worldwide.
Create realistic group photos by compositing individual people into a background scene using Google Gemini. Upload one background photo and multiple subject photos, remove backgrounds for subjects, arrange them, and generate a final composite image.
Live Production: https://posecompose.com (canonical) and https://www.posecompose.com (content host)
- Overview
- Features
- Architecture
- Frontend (Vite/React/TS)
- Backend API Proxy (Express)
- Infrastructure (Nginx, TLS, domains)
- Local Development
- Build & Artifacts
- Deployments
- Configuration
- Security
- Caching & Performance
- Operational Runbook
- Troubleshooting
- Directory Structure
- Roadmap / Future Improvements
PoseCompose is a static, single‑page application (SPA) built with Vite + React + TypeScript and styled with Tailwind + shadcn/ui. It communicates with a small server‑side Node/Express proxy that calls Google’s Generative Language (Gemini) API using a server‑held API key. The server key is never embedded in the browser bundle or exposed to clients.
- Upload a background photo (or choose a sample scene).
- Upload multiple person photos; remove backgrounds per person.
- Arrange (order) selected people for composition.
- Choose output aspect ratios and social formats.
- Generate a final composite image via the Gemini API.
- Download/view generated results.
- Frontend: Vite + React + TypeScript SPA compiled to static assets (HTML/CSS/JS) in
dist/. - Backend: Node/Express service running on
127.0.0.1:4005provides a single endpoint:POST /api/gemini/generate-content→ forwards to Google’s Gemini generateContent endpoint using the server‑held API key.
- Nginx:
- Serves static assets for the site (domain root).
- Proxies
/api/requests to the backend service. - TLS is provided by Let’s Encrypt via Certbot; HSTS is enabled.
- Apex domain redirects permanently to the www host for canonicalization.
- Entry:
index.html, source undersrc/. - UI toolkit: Tailwind CSS + shadcn/ui components.
- Key components:
GroupPhotoGenerator.tsx– orchestrates steps and generation flow.BackgroundUpload.tsx– background chooser and file uploads.PersonUpload.tsx– person photo uploads; triggers background removal.PersonSelection.tsx– selection and ordering of subjects.ResultDisplay.tsx– shows/completes the generated composite.FormatSelection.tsx– social format presets and aspect ratios.
- Image processing (client‑side):
- Background removal is done via API calls to Gemini.
- People images are drawn side‑by‑side on a temporary canvas and then composited into a final canvas sized to the target aspect ratio. The stitched “people strip” is exported as JPEG to ensure compatibility with the upstream API.
- The final request sends two inline images to Gemini: the background (JPEG) and the stitched people (JPEG).
- Location on server:
/opt/posecompose-api. - Service file:
/etc/systemd/system/posecompose-api.service(enabled at boot). - Env file (owner root, mode 600):
/etc/posecompose-api.envcontaining:GEMINI_API_KEY=<YOUR_SERVER_SIDE_KEY>(stored securely, never sent to clients)PORT=4005
- Server code:
server.jsuses Express JSON body parsing and forwards requests to:https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent- API key is supplied via
x-goog-api-keyheader.
- Health endpoint:
GET /healthz→{ ok: true }. - JSON body limit: 30 MB (to match Nginx limits and large base64 payloads).
- Static site root:
/var/www/apps/posecompose(deployed build artifacts). - Vhost:
/etc/nginx/sites-available/posecompose.com(symlinked insites-enabled). - Canonical host:
www.posecompose.comserves content; apexposecompose.comredirects 301 tohttps://www.posecompose.com. - TLS: Let’s Encrypt/Certbot, auto‑renewal timer managed by systemd.
- Security headers: HSTS, X‑Content‑Type‑Options, X‑Frame‑Options, Referrer‑Policy.
- Caching:
- HTML (root/index.html):
Cache-Control: no-store, no-cache, must-revalidate, max-age=0. - Assets (
.css/.js/.png/.jpg/.jpeg/.gif/.svg/.ico/.webmanifest):Cache-Control: public, max-age=604800, immutablewith 7‑day expiry.
- HTML (root/index.html):
- Favicon: explicitly disabled (returns 204 with
no-store) to avoid stale icons.
Requirements
- Node.js 18+ and npm
Install and run
npm ci
npm run devBuild for production
npm run buildOutputs to dist/ with hashed assets and index.html for static hosting at a domain root. If hosting under a subpath (e.g., /apps/posecompose/), build with a base path:
npm run build -- --base=/apps/posecompose/Static files
- Deployed to:
/var/www/apps/posecompose - Helper command on the server:
/usr/local/bin/deploy_static /root/Projects/apps/posecompose-repo/dist posecomposeNginx vhost (summary)
- Content host (HTTPS):
www.posecompose.comserves from/var/www/apps/posecomposeand proxies/api/. - Apex redirect (HTTPS):
posecompose.com→https://www.posecompose.com$request_uri. - HTTP: both apex and www redirect to HTTPS www.
Backend service management
# Check status
systemctl status posecompose-api.service
# View logs
journalctl -u posecompose-api -n 200 -f
# Restart after changes
sudo systemctl restart posecompose-api.serviceNginx operations
sudo nginx -t && sudo systemctl reload nginxEnvironment / Secrets
- Google Gemini API key is stored server‑side in
/etc/posecompose-api.envand read by the backend service. - Do not embed keys in the frontend; the app calls the server proxy at
/api/gemini/generate-content.
Body/Request Sizes
- Nginx
client_max_body_size= 30 MB on the vhost. - Express JSON limit = 30 MB.
Domains & Canonicalization
- Canonical content domain:
www.posecompose.com. - Apex
posecompose.comalways 301 redirects to the www host to avoid split caching and ensure consistent linking.
- API key protection: Key exists only in
/etc/posecompose-api.env, never sent to the client or stored in static assets. - TLS + HSTS: Enforced via Nginx configuration; renewals handled by Certbot.
- Favicon disabled: Prevents stale browser caching of icons when branding changes.
- Optional hardening not yet enabled (candidates):
- Content Security Policy (CSP) tuned for the current stack.
- Nginx rate‑limiting on
/api/to mitigate abuse.
- HTML is marked as no‑store/no‑cache to prevent stale app shells.
- Static assets are long‑cached and content‑hashed, safe to cache for 7 days.
- Client uses canvas compositing and exports JPEG (quality 0.9) to reduce payload size and maximize API compatibility.
The application includes Google Analytics (G-E1EJSR0NEX) for tracking:
- Page views and user sessions
- User interactions and conversion events
- Traffic sources and user behavior
- Performance metrics
The tracking code is loaded in index.html and automatically tracks page views. No additional configuration required.
Deploy a new build
cd /root/Projects/apps/posecompose-repo
npm ci
npm run build
/usr/local/bin/deploy_static dist posecompose
sudo nginx -t && sudo systemctl reload nginxRotate the API key
sudo editor /etc/posecompose-api.env # update GEMINI_API_KEY
sudo systemctl restart posecompose-api.serviceCheck backend health
curl -s http://127.0.0.1:4005/healthzSymptoms and remedies:
-
404 on
/api/gemini/generate-content:- Ensure Nginx keeps the
/api/prefix intact:proxy_pass http://127.0.0.1:4005;(no trailing slash). - Confirm backend is running:
systemctl status posecompose-api.service.
- Ensure Nginx keeps the
-
413 Request Entity Too Large:
- Increase Nginx
client_max_body_sizeand Express JSON limit (both set to 30 MB here).
- Increase Nginx
-
400 INVALID_ARGUMENT from Gemini:
- Ensure images are sent as JPEG (no alpha) and sizes are reasonable. The app exports stitched people as JPEG.
- Retry with smaller images or fewer subjects if bandwidth‑constrained.
-
Apex shows old version while www is fresh:
- This app enforces apex → www redirect to avoid split cache. If you ever disable the redirect, keep HTML no‑store headers.
apps/posecompose-repo/
├─ index.html
├─ package.json
├─ vite.config.ts
├─ public/
│ ├─ placeholder.svg
│ └─ robots.txt
├─ src/
│ ├─ components/
│ │ ├─ GroupPhotoGenerator.tsx
│ │ ├─ BackgroundUpload.tsx
│ │ ├─ PersonUpload.tsx
│ │ ├─ PersonSelection.tsx
│ │ ├─ ResultDisplay.tsx
│ │ ├─ FormatSelection.tsx
│ │ └─ ui/*
│ └─ main.tsx …
└─ dist/ (build output)
- Add CSP and strict security headers tailored to current external resources (Google Fonts, etc.).
- Implement Nginx rate limiting for
/api/to throttle abusive traffic. - Add GitHub Actions workflow for automated build/deploy over SSH.
- Add client‑side downscaling of very large uploads to reduce latency.
- Optional branding (favicon/social image) pipeline.