# Monorepo Architecture Plan **Datum:** 2025-10-08 **Status:** Planning Phase ## Executive Summary Wir bauen ein **PNPM Workspace Monorepo** mit drei unabhängigen Apps: 1. **Mobile App** (React Native) - iOS & Android 2. **Web App** (SvelteKit) - Hauptanwendung 3. **Landing Page** (Astro) - Marketing Website Alle Apps teilen gemeinsame TypeScript Packages für maximalen Code Reuse bei minimaler Komplexität. --- ## Architektur Übersicht ``` picture/ ├── apps/ │ ├── mobile/ # React Native (Expo) │ │ ├── app/ # Expo Router │ │ ├── components/ │ │ ├── hooks/ │ │ ├── package.json │ │ └── ...existing code... │ │ │ ├── web/ # SvelteKit │ │ ├── src/ │ │ │ ├── lib/ │ │ │ │ ├── components/ │ │ │ │ └── stores/ │ │ │ ├── routes/ │ │ │ │ ├── +layout.svelte │ │ │ │ ├── +page.svelte │ │ │ │ ├── auth/ │ │ │ │ ├── images/ │ │ │ │ └── profile/ │ │ │ └── app.html │ │ ├── static/ │ │ ├── svelte.config.js │ │ └── package.json │ │ │ └── landing/ # Astro │ ├── src/ │ │ ├── components/ │ │ │ ├── Hero.astro │ │ │ ├── Features.astro │ │ │ ├── Pricing.astro │ │ │ └── Footer.astro │ │ ├── layouts/ │ │ │ └── Layout.astro │ │ ├── pages/ │ │ │ ├── index.astro # Homepage │ │ │ ├── features.astro # Features Page │ │ │ ├── pricing.astro # Pricing │ │ │ └── about.astro # About │ │ └── styles/ │ ├── public/ │ ├── astro.config.mjs │ └── package.json │ ├── packages/ │ ├── shared/ # Core Business Logic │ │ ├── src/ │ │ │ ├── types/ │ │ │ │ ├── database.ts # Supabase Types │ │ │ │ ├── models.ts # App Models │ │ │ │ └── index.ts │ │ │ ├── api/ │ │ │ │ ├── supabase.ts # Supabase Config │ │ │ │ ├── images.ts # Image API │ │ │ │ ├── auth.ts # Auth API │ │ │ │ └── index.ts │ │ │ ├── utils/ │ │ │ │ ├── image.ts # Image Utils │ │ │ │ ├── validation.ts # Validation │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── package.json │ │ └── tsconfig.json │ │ │ └── ui/ # Shared Design Tokens (optional) │ ├── src/ │ │ ├── colors.ts │ │ ├── typography.ts │ │ └── index.ts │ └── package.json │ ├── package.json # Root Package.json ├── pnpm-workspace.yaml # PNPM Workspace Config ├── .npmrc # NPM Config ├── tsconfig.base.json # Base TypeScript Config └── .env.example # Environment Variables Template ``` --- ## App Details ### 1. Mobile App (React Native + Expo) **Zweck:** Native iOS & Android App **Tech Stack:** - React Native 0.81 - Expo SDK 54 - Expo Router (File-based) - NativeWind (Tailwind) - Zustand (State) - Supabase Client **Features:** - ✅ Auth (Login/Signup) - ✅ Image Upload - ✅ Image Gallery - ✅ Image Detail View mit Zoom - ✅ Profile Management - ✅ Offline Support (später) **Deployment:** - EAS Build (Expo Application Services) - App Store & Google Play ### 2. Web App (SvelteKit) **Zweck:** Vollständige Web-Anwendung (Desktop + Mobile Web) **Tech Stack:** - SvelteKit 2.x - Svelte 5 - Tailwind CSS - Supabase Client - Vite - TypeScript **Features:** - ✅ Auth (Login/Signup) - ✅ Image Upload (Drag & Drop) - ✅ Image Gallery (Masonry Layout) - ✅ Image Detail View - ✅ Profile Management - ✅ SSR für SEO - ✅ Image Optimization **Routing:** ``` / # Home (Gallery) /auth # Login/Signup /auth/callback # OAuth Callback /images # Images Grid /images/[id] # Image Detail /profile # User Profile /settings # Settings ``` **Deployment:** - Cloudflare Pages (empfohlen) - Alternativ: Vercel, Netlify ### 3. Landing Page (Astro) **Zweck:** Marketing Website, SEO-optimiert, statisch **Tech Stack:** - Astro 4.x - Tailwind CSS - TypeScript - Optional: Svelte/React Components **Features:** - ✅ Hero Section - ✅ Features Overview - ✅ Pricing Page - ✅ About Page - ✅ Blog (optional) - ✅ SEO Optimiert - ✅ Ultraschnell (Static) **Seiten:** ``` / # Homepage /features # Features Detail /pricing # Pricing Plans /about # About Us /blog/ # Blog (optional) /docs/ # Documentation (optional) ``` **Warum Astro?** - ⚡ Ultraschnell (0 JS by default) - 🎯 Perfect für Marketing - 🔍 SEO-optimiert out of the box - 🎨 Kann Svelte/React Components nutzen - 📦 Tiny Bundle Sizes **Deployment:** - Cloudflare Pages (empfohlen) - Netlify - Vercel --- ## Shared Packages ### Package: `@picture/shared` **Zweck:** Core Business Logic, Types, API **Was wird geteilt:** ```typescript // Types export type { Database, Image, User, Album } from './types'; // API Clients export { createSupabaseClient } from './api/supabase'; export { ImageAPI } from './api/images'; export { AuthAPI } from './api/auth'; // Utils export { calculateAspectRatio, validateImageSize, formatFileSize, generateBlurhash } from './utils/image'; export { validateEmail, validatePassword, sanitizeInput } from './utils/validation'; export { formatDate, formatRelativeTime } from './utils/date'; ``` **Struktur:** ``` packages/shared/ ├── src/ │ ├── types/ │ │ ├── database.ts # Supabase Generated │ │ ├── models.ts # App Models │ │ └── index.ts │ ├── api/ │ │ ├── base.ts # Base API Client │ │ ├── supabase.ts # Supabase Setup │ │ ├── images.ts # Image Operations │ │ ├── auth.ts # Auth Operations │ │ └── index.ts │ ├── utils/ │ │ ├── image.ts # Image Utils │ │ ├── validation.ts # Input Validation │ │ ├── date.ts # Date Formatting │ │ └── index.ts │ ├── constants/ │ │ ├── config.ts # App Config │ │ └── index.ts │ └── index.ts ├── package.json └── tsconfig.json ``` ### Package: `@picture/ui` (Optional) **Zweck:** Shared Design Tokens **Was wird geteilt:** ```typescript // Colors export const colors = { dark: { bg: '#000000', surface: '#242424', elevated: '#2a2a2a', border: '#383838', }, // ... }; // Typography export const typography = { fonts: { body: 'Inter, system-ui, sans-serif', heading: 'Inter, system-ui, sans-serif', }, sizes: { xs: '0.75rem', sm: '0.875rem', base: '1rem', // ... } }; // Spacing export const spacing = { xs: '0.25rem', sm: '0.5rem', md: '1rem', // ... }; ``` --- ## PNPM Workspace Setup ### 1. Root `package.json` ```json { "name": "picture-monorepo", "version": "1.0.0", "private": true, "scripts": { "dev": "pnpm run --parallel dev", "dev:mobile": "pnpm --filter @picture/mobile dev", "dev:web": "pnpm --filter @picture/web dev", "dev:landing": "pnpm --filter @picture/landing dev", "build": "pnpm run --recursive build", "build:mobile": "pnpm --filter @picture/mobile build", "build:web": "pnpm --filter @picture/web build", "build:landing": "pnpm --filter @picture/landing build", "lint": "pnpm run --recursive lint", "type-check": "pnpm run --recursive type-check", "clean": "pnpm run --recursive clean && rm -rf node_modules" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.8.3", "prettier": "^3.2.5", "eslint": "^9.25.1" } } ``` ### 2. `pnpm-workspace.yaml` ```yaml packages: - 'apps/*' - 'packages/*' ``` ### 3. `.npmrc` ```ini # Use pnpm for all installs auto-install-peers=true shamefully-hoist=true strict-peer-dependencies=false ``` ### 4. `tsconfig.base.json` ```json { "compilerOptions": { "target": "ES2022", "module": "ESNext", "lib": ["ES2022"], "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, "paths": { "@picture/shared": ["./packages/shared/src"], "@picture/ui": ["./packages/ui/src"] } } } ``` --- ## Migration Steps ### Phase 1: Monorepo Setup (Tag 1, ~3h) **1.1 PNPM Installation** ```bash # Install PNPM globally npm install -g pnpm # Verify pnpm --version ``` **1.2 Ordnerstruktur** ```bash # Create apps directory mkdir -p apps # Create packages directory mkdir -p packages/shared/src mkdir -p packages/ui/src # Move existing code mv app apps/mobile/ mv components apps/mobile/ mv hooks apps/mobile/ mv services apps/mobile/ mv utils apps/mobile/ mv store apps/mobile/ # ... all other mobile files ``` **1.3 Workspace Config** - `pnpm-workspace.yaml` erstellen - Root `package.json` anpassen - `.npmrc` erstellen - `tsconfig.base.json` erstellen **1.4 Mobile App Package** ```json // apps/mobile/package.json { "name": "@picture/mobile", "version": "1.0.0", "main": "expo-router/entry", "scripts": { "dev": "expo start", "ios": "expo run:ios", "android": "expo run:android", "build:dev": "eas build --profile development", "lint": "eslint .", "type-check": "tsc --noEmit" }, "dependencies": { "@picture/shared": "workspace:*", // ... existing dependencies } } ``` ### Phase 2: Shared Package (Tag 1-2, ~4h) **2.1 Package Setup** ```json // packages/shared/package.json { "name": "@picture/shared", "version": "1.0.0", "type": "module", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { ".": "./src/index.ts", "./types": "./src/types/index.ts", "./api": "./src/api/index.ts", "./utils": "./src/utils/index.ts" }, "scripts": { "type-check": "tsc --noEmit", "lint": "eslint src" }, "dependencies": { "@supabase/supabase-js": "^2.38.4" }, "devDependencies": { "typescript": "^5.8.3" } } ``` **2.2 Supabase Types generieren** ```bash # In packages/shared/ npx supabase gen types typescript \ --project-id mjuvnnjxwfwlmxjsgkqu \ > src/types/database.ts ``` **2.3 Basic API Client** ```typescript // packages/shared/src/api/supabase.ts import { createClient } from '@supabase/supabase-js'; import type { Database } from '../types/database'; export function createSupabaseClient(url: string, key: string) { return createClient(url, key, { auth: { persistSession: true, autoRefreshToken: true, }, }); } // packages/shared/src/api/images.ts import type { SupabaseClient } from '@supabase/supabase-js'; import type { Database } from '../types/database'; export class ImageAPI { constructor(private client: SupabaseClient) {} async getImages(userId: string) { const { data, error } = await this.client .from('images') .select('*') .eq('user_id', userId) .order('created_at', { ascending: false }); if (error) throw error; return data; } async getImage(id: string) { const { data, error } = await this.client .from('images') .select('*') .eq('id', id) .single(); if (error) throw error; return data; } async uploadImage(file: File | Blob, userId: string) { // Upload logic } async deleteImage(id: string) { const { error } = await this.client .from('images') .delete() .eq('id', id); if (error) throw error; } } ``` **2.4 Utils extrahieren** ```typescript // packages/shared/src/utils/image.ts export function calculateAspectRatio(width: number, height: number): number { return width / height; } export function validateImageSize(size: number, maxSize: number = 10 * 1024 * 1024): boolean { return size <= maxSize; } export function formatFileSize(bytes: number): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; } // packages/shared/src/utils/validation.ts export function validateEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } export function validatePassword(password: string): { valid: boolean; errors: string[]; } { const errors: string[] = []; if (password.length < 8) { errors.push('Password must be at least 8 characters'); } if (!/[A-Z]/.test(password)) { errors.push('Password must contain uppercase letter'); } if (!/[a-z]/.test(password)) { errors.push('Password must contain lowercase letter'); } if (!/[0-9]/.test(password)) { errors.push('Password must contain number'); } return { valid: errors.length === 0, errors }; } ``` ### Phase 3: SvelteKit Setup (Tag 2-3, ~6h) **3.1 Create SvelteKit App** ```bash cd apps pnpm create svelte@latest web # Choose: # - Skeleton project # - TypeScript # - ESLint + Prettier # - Vitest (optional) ``` **3.2 Install Dependencies** ```bash cd web pnpm add @supabase/supabase-js @supabase/ssr pnpm add -D tailwindcss autoprefixer postcss pnpm add -D @sveltejs/adapter-cloudflare # or adapter-auto # Add shared package pnpm add @picture/shared@workspace:* ``` **3.3 Configure Tailwind** ```bash npx tailwindcss init -p ``` ```javascript // tailwind.config.js export default { content: ['./src/**/*.{html,js,svelte,ts}'], theme: { extend: {}, }, plugins: [], }; ``` **3.4 Package.json** ```json // apps/web/package.json { "name": "@picture/web", "version": "1.0.0", "type": "module", "scripts": { "dev": "vite dev", "build": "vite build", "preview": "vite preview", "type-check": "svelte-check --tsconfig ./tsconfig.json", "lint": "eslint ." }, "dependencies": { "@picture/shared": "workspace:*", "@supabase/supabase-js": "^2.38.4", "@supabase/ssr": "^0.1.0" }, "devDependencies": { "@sveltejs/adapter-cloudflare": "^4.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", "svelte": "^5.0.0", "svelte-check": "^3.6.0", "tailwindcss": "^3.4.0", "typescript": "^5.8.3", "vite": "^5.0.0" } } ``` **3.5 Supabase Integration** ```typescript // apps/web/src/lib/supabase.ts import { createSupabaseClient } from '@picture/shared/api'; import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; export function getSupabase() { return createSupabaseClient( PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY ); } ``` **3.6 Basic Routes** ```svelte

Welcome to Picture

{#each images as image}
{image.url}
{/each} ``` ### Phase 4: Astro Landing Page (Tag 3-4, ~4h) **4.1 Create Astro Project** ```bash cd apps pnpm create astro@latest landing # Choose: # - Empty template # - TypeScript (Strict) # - Install dependencies ``` **4.2 Install Dependencies** ```bash cd landing pnpm add -D tailwindcss autoprefixer postcss pnpm add -D @astrojs/tailwind pnpm add -D @astrojs/sitemap # For SEO # Optional: Svelte integration for interactive components pnpm add -D @astrojs/svelte ``` **4.3 Configure Astro** ```javascript // apps/landing/astro.config.mjs import { defineConfig } from 'astro/config'; import tailwind from '@astrojs/tailwind'; import sitemap from '@astrojs/sitemap'; import svelte from '@astrojs/svelte'; export default defineConfig({ site: 'https://picture.app', // Your domain integrations: [ tailwind(), sitemap(), svelte() // Optional ], output: 'static', // Static site generation }); ``` **4.4 Package.json** ```json // apps/landing/package.json { "name": "@picture/landing", "version": "1.0.0", "type": "module", "scripts": { "dev": "astro dev", "build": "astro build", "preview": "astro preview", "astro": "astro" }, "dependencies": { "@astrojs/sitemap": "^3.0.0", "@astrojs/svelte": "^5.0.0", "@astrojs/tailwind": "^5.0.0", "astro": "^4.0.0", "svelte": "^5.0.0", "tailwindcss": "^3.4.0" } } ``` **4.5 Basic Pages** ```astro --- // apps/landing/src/pages/index.astro import Layout from '../layouts/Layout.astro'; import Hero from '../components/Hero.astro'; import Features from '../components/Features.astro'; --- ``` ```astro --- // apps/landing/src/layouts/Layout.astro interface Props { title: string; } const { title } = Astro.props; --- {title} ``` ```astro --- // apps/landing/src/components/Hero.astro ---

Beautiful Image Management

Store, organize, and share your images with Picture

Get Started
``` --- ## Development Workflow ### Starting all apps ```bash # Root directory pnpm dev ``` ### Starting individual apps ```bash pnpm dev:mobile # React Native on Expo (Port 8081) pnpm dev:web # SvelteKit on http://localhost:5173 pnpm dev:landing # Astro on http://localhost:4321 ``` **Landing Page Details:** - Framework: Astro 5.x - Dev Server: http://localhost:4321 - Hot Module Replacement (HMR) aktiviert - Tailwind CSS integriert - Build Output: `dist/` (static files) ### Building ```bash pnpm build # Build all pnpm build:web # Build web only pnpm build:landing # Build landing only ``` ### Type Checking ```bash pnpm type-check # Check all ``` ### Linting ```bash pnpm lint # Lint all ``` --- ## Deployment Strategy ### Mobile (React Native) ```bash cd apps/mobile eas build --platform ios --profile production eas build --platform android --profile production eas submit --platform ios eas submit --platform android ``` ### Web (SvelteKit) **Cloudflare Pages (empfohlen):** ```bash cd apps/web pnpm build # Deploy via Cloudflare Pages Dashboard # Build command: pnpm build # Output directory: build ``` **Environment Variables:** - `PUBLIC_SUPABASE_URL` - `PUBLIC_SUPABASE_ANON_KEY` ### Landing (Astro) **Cloudflare Pages:** ```bash cd apps/landing pnpm build # Deploy via Cloudflare Pages Dashboard # Build command: pnpm build # Output directory: dist ``` **Static Site - super einfach:** - Build einmal - Upload zu beliebigem Static Host - Kein Server needed --- ## Environment Variables ### Root `.env.example` ```bash # Supabase SUPABASE_URL=https://your-project.supabase.co SUPABASE_ANON_KEY=your-anon-key SUPABASE_SERVICE_KEY=your-service-key # URLs MOBILE_APP_URL=exp://localhost:8081 WEB_APP_URL=http://localhost:5173 LANDING_URL=http://localhost:4321 # Feature Flags FEATURE_UPLOAD_ENABLED=true FEATURE_ARCHIVE_ENABLED=true ``` ### App-specific `.env` ```bash # apps/mobile/.env EXPO_PUBLIC_SUPABASE_URL=$SUPABASE_URL EXPO_PUBLIC_SUPABASE_ANON_KEY=$SUPABASE_ANON_KEY # apps/web/.env PUBLIC_SUPABASE_URL=$SUPABASE_URL PUBLIC_SUPABASE_ANON_KEY=$SUPABASE_ANON_KEY # apps/landing/.env # No env needed (static site) ``` --- ## Code Sharing Matrix | Feature | Mobile | Web | Landing | Shared | |---------|--------|-----|---------|--------| | Supabase Types | ✅ | ✅ | ❌ | ✅ | | API Client | ✅ | ✅ | ❌ | ✅ | | Auth Logic | ✅ | ✅ | ❌ | ✅ | | Image Utils | ✅ | ✅ | ❌ | ✅ | | Validation | ✅ | ✅ | ❌ | ✅ | | UI Components | ❌ | ❌ | ❌ | ❌ | | Design Tokens | ✅ | ✅ | ✅ | ✅ (optional) | **Code Reuse: ~35-40%** --- ## Timeline ### Phase 1: Monorepo Setup (Tag 1) - [x] PNPM Installation - [ ] Ordnerstruktur erstellen - [ ] Mobile Code migrieren - [ ] Workspace Config - [ ] Test: `pnpm dev:mobile` funktioniert **Time:** ~3h ### Phase 2: Shared Package (Tag 1-2) - [ ] Package erstellen - [ ] Supabase Types generieren - [ ] API Client bauen - [ ] Utils extrahieren - [ ] Mobile nutzt `@picture/shared` **Time:** ~4h ### Phase 3: SvelteKit App (Tag 2-4) - [ ] App initialisieren - [ ] Tailwind Setup - [ ] Supabase Integration - [ ] Basic Routes (Home, Auth) - [ ] Images Grid - [ ] Image Detail - [ ] Profile Page **Time:** ~10h ### Phase 4: Astro Landing (Tag 4-5) - [ ] App initialisieren - [ ] Tailwind Setup - [ ] Homepage - [ ] Features Page - [ ] Pricing Page - [ ] About Page **Time:** ~6h ### Phase 5: Deploy (Tag 5-6) - [ ] Mobile: EAS Build - [ ] Web: Cloudflare Pages - [ ] Landing: Cloudflare Pages - [ ] Environment Variables - [ ] Domains Setup **Time:** ~4h **Total:** 5-6 Tage --- ## Advantages ✅ **Maximale Unabhängigkeit** - Jede App kann unabhängig deployed werden - Kein Vendor Lock-in - Web Standards überall ✅ **Code Reuse** - ~40% Code geteilt - Types einmal definieren - API Logic zentral ✅ **Optimal für Zweck** - Mobile: Native Experience - Web: Full App mit SSR - Landing: Ultraschnell, SEO ✅ **Einfache Wartung** - Clear Separation - Shared Logic zentral - Easy Updates ✅ **Skalierbar** - Neue Apps einfach hinzufügen - Packages wachsen organisch - CI/CD pro App --- ## Tech Stack Summary ```yaml Mobile (React Native): Framework: Expo SDK 54 Routing: Expo Router Styling: NativeWind State: Zustand Deploy: EAS Build Web (SvelteKit): Framework: SvelteKit 2.x / Svelte 5 Styling: Tailwind CSS Build: Vite Deploy: Cloudflare Pages Landing (Astro): Framework: Astro 4.x Styling: Tailwind CSS Components: Astro + optional Svelte Deploy: Cloudflare Pages (Static) Shared: Language: TypeScript 5.x Backend: Supabase Package Manager: PNPM Workspace: PNPM Workspaces ``` --- ## Next Steps 1. ✅ Planung Complete 2. ⏭️ PNPM installieren 3. ⏭️ Ordnerstruktur aufbauen 4. ⏭️ Mobile Code migrieren 5. ⏭️ Shared Package erstellen 6. ⏭️ SvelteKit initialisieren 7. ⏭️ Astro initialisieren --- **Bereit?** Lass uns mit Phase 1 starten! 🚀 **Autor:** Claude Code **Status:** Ready to implement **Last Updated:** 2025-10-08