~/lebenslauf/projekte/remi
cat projekte/remi.md

Remi.

Rezept- und Haushalts-App für Web, iOS und Android. Ein Foto von einem Kochbuch oder ein Link zu einem Rezept reicht — den Rest macht die App: Titel, Zeit, Zutaten mit Mengen und Einheiten, Zubereitungsschritte. Sammlungen, Einkaufsliste und Wochenplan gehören dabei nicht einer Person, sondern einem Haushalt.

Rolle
Konzept, Entwicklung
und Betrieb — allein
Plattformen
Web · iOS · Android
eine Codebasis
Stack
React Native, Expo,
TypeScript, Supabase
Status
live unter
remi.netzer.cloud
01

Ausgangslage

Warum die App

Rezepte liegen überall verstreut: als Foto vom Kochbuch, als Link in einem Chat, als Screenshot. Das Abtippen in eine Rezept-App ist genau die Arbeit, die niemand macht — also bleiben die Rezepte in der Fotomediathek liegen und werden nie wieder gefunden.

Dazu kommt ein zweites Problem, das die meisten Rezept-Apps ignorieren: Kochen ist selten eine Einzeltätigkeit. Wer zusammen wohnt, plant zusammen und kauft zusammen ein. Eine App, in der jede Person ihre eigene Einkaufsliste pflegt, löst das Problem nicht, sondern verdoppelt es.

Remi setzt an beiden Punkten an: Die Erfassung übernimmt ein KI-Backend, und alle geteilten Daten hängen an einem Haushalt statt an einem einzelnen Konto.

02

Funktionen

Was die App kann

Rezept aus Foto

Foto aufnehmen oder aus der Mediathek wählen; das Backend liefert ein strukturiertes Rezept zurück, das sich vor dem Speichern noch bearbeiten lässt.

Rezept aus Link

Adresse einer Rezeptseite einfügen — Titel, Zeit, Zutaten und Schritte werden extrahiert, inklusive Vorschaubild und Verweis auf die Originalquelle.

Manuell erfassen

Vollständiger Editor für Zutaten mit Menge und Einheit sowie für Zubereitungsschritte — auch für alles, was von Hand schneller geht.

Sammlungen

Rezepte in eigenen Sammlungen ordnen. Sammlungsnamen sind pro Haushalt eindeutig, damit zwei Personen nicht zwei „Favoriten" anlegen.

Einkaufsliste

Zutaten aus einem Rezept direkt auf die gemeinsame Liste legen, abhaken im Laden — für alle im Haushalt derselbe Stand.

Wochenplan

Rezepte auf Wochentage legen. Einträge, die älter als sieben Tage sind, räumt eine geplante Serverfunktion nachts selbst auf.

Gemeinsame Haushalte

Per Einladungscode in einen Haushalt kommen. Beim Beitritt wandern die eigenen Daten mit, beim Austritt nimmt man mit, was man selbst angelegt hat.

Teilen nach außen

Einzelne Rezepte gehen als Kopie in ein Postfach an Personen außerhalb des Haushalts; die Empfänger übernehmen sie und bearbeiten sie frei.

Zweisprachig

Deutsch und Englisch, 349 Übersetzungsschlüssel je Sprache. Die Sprache geht auch an das KI-Backend, damit die Extraktion passend antwortet.

03

KI-gestützte Erfassung

Kernstück

Die App spricht ein eigenes Backend mit zwei Endpunkten an: extract-recipe für Links und extract-recipe-from-image für Fotos. Gesendet werden das Bild als Base64 beziehungsweise die Adresse — dazu immer die aktuelle Oberflächensprache, damit das Ergebnis in derselben Sprache zurückkommt.

Interessant ist weniger der Aufruf als das, was danach passiert. Modelle antworten nicht zuverlässig im selben Schema: mal titel, mal title, mal name; Zutaten mal als Objekt mit Menge und Einheit, mal als bloßer Text. Die App normalisiert deshalb jedes Feld gegen mehrere mögliche Schlüssel und fällt sauber zurück, statt an einer unerwarteten Antwort zu zerbrechen.

add-recipe-image.tsx
// Antwort tolerant lesen: deutsche wie englische Schlüssel,
// Zutaten als Objekt oder als reiner Text.
const title = r.titel ?? r.title ?? r.name ?? null;
const timeRaw = r.zeit_minuten ?? r.time ?? 30;

const ingredients = rawIngredients.map((ing, i) => {
  if (typeof ing === 'string') return { name: ing, amount: '', unit: '' };
  return {
    name: ing.name ?? ing.zutat ?? fallback(i),
    amount: String(ing.menge ?? ing.amount ?? ''),
    unit: ing.einheit ?? ing.unit ?? '',
  };
});

Das Ergebnis landet nicht direkt in der Datenbank, sondern im Editor. Der Mensch bestätigt — und korrigiert, wo die Extraktion danebenlag. Das ist die ehrlichere Umsetzung als ein Ergebnis, das ungeprüft gespeichert wird: Eine falsch erkannte Mengenangabe fällt beim Kochen auf, nicht beim Speichern.

Fehlt die Backend-Adresse zur Bauzeit, meldet die App das offen („nicht konfiguriert") statt in einen Fehler zu laufen — die manuelle Erfassung funktioniert unabhängig davon weiter.

04

Architektur

Wie es gebaut ist
[01]

Eine Codebasis für drei Plattformen

React Native 0.81 mit React 19 und Expo 54, Navigation über expo-router mit dateibasierten Routen — 21 Bildschirme, von den Tabs über die Rezeptdetails bis zur Haushaltsverwaltung. Web entsteht als statischer Export aus derselben Quelle wie die nativen Apps. Durchgehend TypeScript, rund 53 Dateien.

[02]

Supabase als Rückgrat

Postgres für Rezepte, Sammlungen, Einkaufsliste und Wochenplan, dazu Authentifizierung per Magic Link — auf Web mit der eigenen Herkunftsadresse, nativ über ein eigenes App-Schema. Sitzungen liegen in expo-secure-store statt im Klartextspeicher.

[03]

Berechtigungen in der Datenbank, nicht in der App

Row Level Security regelt den Zugriff direkt in Postgres: Sehen darf eine Zeile, wem sie gehört oder wer im selben Haushalt ist. Die Regel steht damit an einer Stelle und gilt für jeden Client — auch für einen, der sich nicht an die App-Logik hält.

[04]

Migration, die man zweimal laufen lassen kann

Die Haushalts-Funktion kam nachträglich dazu: eine idempotente SQL-Migration legt Tabellen, Funktionen und Regeln an. Bis sie eingespielt ist, läuft die App unverändert weiter — die Haushalts-Funktionen sind dann schlicht nicht verfügbar, alle Daten bleiben persönlich. Kein Big-Bang-Umstieg.

[05]

Aufräumen ohne Server

Eine Edge Function in Deno löscht Wochenplan-Einträge, die älter als sieben Tage sind; ausgelöst wird sie täglich um drei Uhr über einen Datenbank-Cronjob. Sie nimmt nur POST an, damit sie nicht versehentlich per Aufruf im Browser läuft.

[06]

Auslieferung

Statischer Export auf Vercel. Weil pro Route eine eigene Datei entsteht, braucht es zwei Regeln in vercel.json: saubere Adressen ohne Dateiendung und eine Umleitung für dynamische Routen wie /recipe/<id> — sonst enden geteilte Links und jeder Neuladen-Klick im 404.

05

Entscheidungen

Und was sie gekostet haben

Haushalt statt Freigabe pro Objekt. Die einfachere Variante wäre gewesen, einzelne Rezepte freizugeben. Das hätte aber bei Einkaufsliste und Wochenplan nicht funktioniert — die sind nur gemeinsam sinnvoll. Der Preis: Beitritt und Austritt müssen Daten verschieben, gleichnamige Sammlungen zusammenführen und beim Austritt sauber trennen, was wem gehört.

Extraktion im Backend, nicht in der App. Der Modellzugriff liegt hinter einem eigenen Dienst. Das kostet einen zusätzlichen Baustein im Betrieb, hält aber Schlüssel aus dem Client heraus — in einer App, die als Web-Bundle ausgeliefert wird, ist alles im Client öffentlich lesbar.

Zwei Extraktionswege statt einem. Foto und Link sind technisch verschieden genug für getrennte Endpunkte, teilen sich aber die Normalisierung und den Editor dahinter. Ein gemeinsamer Endpunkt hätte die Fehlerbehandlung unübersichtlich gemacht.

Icons aus der Schriftart erzeugen. Favicon, Touch-Icon und Manifest-Icons entstehen per Skript aus demselben Zeichensatz wie der Anmeldebildschirm — in drei Größenverhältnissen, weil iOS eine eigene Maske darüberlegt und Android bis zu 20 Prozent am Rand wegschneidet. Anders laufen Icon und App früher oder später auseinander.