In unserem CRM Kanban: Warum wir Monday.com in Astro + Supabase neu aufgebaut haben
Wir zahlten $48/Monat für Monday.com. Drei Plätze, Pro-Plan. Und jede einzelne Woche sagte jemand im Team eine Version von „Ich hasse das." Nicht, weil Monday.com schlecht ist — es ist wirklich beeindruckende Software. Aber es machte etwa 200 Dinge, die wir nicht brauchten, und konnte die 4 Dinge, die wir eigentlich wollten, nicht ganz machen. Also taten wir, was jede Agentur voller Entwickler mit zu vielen Meinungen tun würde: Wir bauten unser eigenes.
Dies ist kein „wir sind klüger als Monday.com"-Artikel. Es ist ein „wir hatten sehr spezifische Anforderungen, einen Stack, den wir bereits kannten, und ein Wochenende"-Artikel. Hier ist die vollständige Geschichte vom Bau eines benutzerdefinierten CRM-Kanban-Boards mit Astro, Supabase und einer gesunden Portion Verachtung für SaaS-Überfrachtung.
Inhaltsverzeichnis
- Warum Monday.com nicht mehr für uns funktionierte
- Was wir eigentlich brauchten
- Stack-Auswahl: Astro + Supabase
- Datenbankdesign: Einfach halten
- Bau des Kanban-Boards
- Echtzeit-Updates mit Supabase Realtime
- Authentifizierung und Row-Level Security
- Bereitstellung und Hosting-Kosten
- Was wir anders machen würden
- FAQ

Warum Monday.com nicht mehr für uns funktionierte
Lassen Sie mich konkret über unsere Frustrationen sein, denn vage Beschwerden über SaaS-Tools sind nutzlos.
Problem 1: Das Datenmodell kämpfte gegen uns. Monday.com denkt in „Boards" und „Items" mit „Spalten." Unsere Agentur denkt in Deals, Kontakte und Projekte — drei unterschiedliche Entitäten mit Beziehungen zueinander. Wir brauchten einen Deal, der auf einen Kontakt verweist, und ein Projekt, das auf einen Deal verweist. Monday.com kann das mit verknüpften Spalten irgendwie tun, aber es ist umständlich. Jedes Mal, wenn jemand einen neuen Deal erstellte, mussten sie ihn manuell mit dem richtigen Kontakt verknüpfen. Leute vergaßen das. Daten wurden unordentlich.
Problem 2: Die Kanban-Ansicht konnte nicht das tun, was wir wollten. Wir brauchten Deals, die nach Phase gruppiert UND farbcodiert nach Quelle (Empfehlung, organisch, Outbound) waren. Die Kanban-Ansicht von Monday.com lässt Sie nur nach einer Statusspalte gruppieren. Das ist alles. Sie können keine zweite visuelle Dimension überlagern, ohne es mit Benennungskonventionen zu hacken.
Problem 3: Geschwindigkeit. Das ist subjektiv, aber Monday.com fühlte sich für das, was wir taten, langsam an. Klicken Sie auf einen Deal, warten Sie auf das Seitenpanel zum Laden, scrollen Sie an Feldern vorbei, die wir nicht verwenden, finden Sie die Notiz, die wir brauchen. Jede Interaktion hatte gerade genug Latenz, um sich wie Reibung anzufühlen.
Problem 4: Kostenentwicklung. Bei $48/Monat für drei Personen ist es nicht teuer. Aber wir blickten auf ein viertes Teammitglied, und Mondays Preisgestaltung springt zu $60/Monat für den Pro-Plan mit 5 Plätzen (Sie können nicht 4 Plätze kaufen). Das sind $720/Jahr für ein Tool, über das wir aktiv klagten.
Der Wendepunkt
Der eigentliche Auslöser war peinlich alltäglich. Ein potenzieller Kunde schrieb uns eine E-Mail an, und zwei Teammitglieder antworteten beide, weil keiner von Monday.com sehen konnte, wer den Lead „beanspruchte". Das Benachrichtigungssystem zeigte es nicht deutlich genug an, und unser hackhafter Workaround, sich selbst zu einer Spalte „Personen" hinzuzufügen, war nicht zuverlässig. Das war, als ich VS Code anstelle von Monday.com öffnete.
Was wir eigentlich brauchten
Bevor wir Code schrieben, verbrachten wir etwa eine Stunde damit, genau aufzulisten, was unser CRM tun musste. Nicht, was schön wäre. Was war wirklich notwendig.
Hier ist die Liste:
- Kanban-Board mit Spalten für Deal-Phasen: Lead → Contacted → Proposal → Negotiation → Won → Lost
- Deal-Karten, die zeigen: Kontaktname, Deal-Wert, Quellen-Tag (farbcodiert), zugewiesenes Teammitglied, Tage in aktueller Phase
- Ziehen und Ablegen zwischen Spalten mit sofortiger Persistierung
- Deal-Detailansicht mit Notizen (Markdown), Kontaktinformationen und einfaches Aktivitätsprotokoll
- Echtzeit-Sync, damit zwei Menschen, die das Board ansehen, denselben Zustand sehen
- Kontaktdatenbank mit grundlegenden Informationen (Name, E-Mail, Unternehmen, Notizen)
- Einfache Auth — nur unser Team, kein öffentlicher Zugriff
Das ist alles. Keine Gantt-Diagramme. Keine Zeitmessung. Keine Automationsmaschine. Keine 47 verschiedenen Spaltentypen. Nur ein Kanban-Board gestützt durch eine echte Datenbank mit echten Beziehungen.
Stack-Auswahl: Astro + Supabase
Wir sind ein Astro-Entwicklungsteam, also war Astro der offensichtliche Ausgangspunkt. Aber es lohnt sich zu erklären, warum es hier eigentlich Sinn macht, denn Astros Ruf als „statischer Site-Generator" unterschätzt es erheblich.
Seit Astro 4.x (und jetzt 5.x im Jahr 2025) ist serverseitiges Rendering mit On-Demand-Routen ein erstklassiges Feature. Sie können vollständig dynamische Anwendungen erstellen. Wir verwenden Astros hybriden Rendering-Modus: Die meisten Seiten werden auf Anfrage servergerendert, aber wir können immer noch Dinge wie die Login-Seite vorrendern.
Für das interaktive Kanban-Board selbst verwenden wir eine React-Island. Dies ist Astros Killer-Feature für Apps wie diese — die Shell der Anwendung (Navigation, Layout, Auth-Checks) wird servergerendert mit null JS, und das Kanban-Board wird als einzelne interaktive Island mit client:load eingebunden.
Supabase war die Datenbankauswahl aus mehreren Gründen:
| Feature | Warum es wichtig war |
|---|---|
| Postgres unter der Haube | Echte relationale Datenbank, echte Fremdschlüssel, echte Abfragen |
| Echtzeit-Abonnements | Integrierte WebSocket-Unterstützung für Live-Updates |
| Row-Level Security (RLS) | Auth-Regeln auf Datenbankebene, nicht nur auf App-Ebene |
| JS-Client-Bibliothek | Saubere API, gute TypeScript-Unterstützung |
| Kostenlos-Tier | Unsere Nutzung passt bequem in Supabase kostenlosen Plan |
| Self-Host-Option | Wenn wir den kostenlosen Tier überschreiten, können wir es selbst ausführen |
Wir zogen kurz andere Optionen in Betracht:
| Option | Warum wir vorbeigin |
|---|---|
| Firebase / Firestore | NoSQL macht relationale Daten unbeholfen. Früher verloren. |
| PlanetScale | Großartig, aber keine integrierte Echtzeit. Bräuchte separate WebSocket-Lösung. |
| Neon + Prisma | Solides Duo, aber Supabase gibt uns Auth + Realtime + DB in einem. |
| Bau auf Next.js | Wir kennen Next.js gut (wir bauen regelmäßig damit), aber für ein internes Tool bedeutete Astros Island-Architektur weniger Client-Side JS für die nicht-interaktiven Teile. |

Datenbankdesign: Einfach halten
Das Schema hat vier Tabellen. Das ist alles.
-- Kontakte: die Menschen und Unternehmen, mit denen wir sprechen
create table contacts (
id uuid default gen_random_uuid() primary key,
name text not null,
email text,
company text,
phone text,
notes text,
created_at timestamptz default now()
);
-- Deals: die Pipeline-Elemente auf unserem Kanban-Board
create table deals (
id uuid default gen_random_uuid() primary key,
contact_id uuid references contacts(id) on delete set null,
title text not null,
value integer, -- in Cent gespeichert
stage text not null default 'lead'
check (stage in ('lead', 'contacted', 'proposal', 'negotiation', 'won', 'lost')),
source text
check (source in ('referral', 'organic', 'outbound', 'repeat', 'other')),
assigned_to uuid references auth.users(id),
position integer not null default 0, -- zum Ordnen innerhalb einer Spalte
stage_entered_at timestamptz default now(),
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- Aktivitätsprotokoll: einfaches Nur-Anfügen-Protokoll
create table activities (
id uuid default gen_random_uuid() primary key,
deal_id uuid references deals(id) on delete cascade,
user_id uuid references auth.users(id),
action text not null, -- 'stage_change', 'note', 'created', usw.
details jsonb,
created_at timestamptz default now()
);
-- Deal-Notizen: Markdown-Notizen an Deals angehängt
create table deal_notes (
id uuid default gen_random_uuid() primary key,
deal_id uuid references deals(id) on delete cascade,
user_id uuid references auth.users(id),
content text not null,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
Das Feld stage_entered_at auf Deals ist eine meiner liebsten kleinen Entscheidungen. Jedes Mal, wenn ein Deal in eine neue Phase wechselt, aktualisieren wir diesen Zeitstempel. Das lässt uns „Tage in aktueller Phase" berechnen, ohne das Aktivitätsprotokoll abzufragen. Einfach, schnell, nützlich.
Das Feld position behandelt die Reihenfolge innerhalb von Kanban-Spalten. Wenn Sie eine Karte zwischen zwei anderen ziehen, berechnen wir einen neuen Positionswert. Wir verwenden ganzzahligen Abstand (Positionen erhöhen sich um 1000), sodass wir selten neu ausgleichen müssen.
Bau des Kanban-Boards
Das Kanban-Board ist eine React-Komponente, die als Astro-Island eingebunden ist. Wir verwendeten @dnd-kit/core zum Ziehen und Ablegen, weil es die zugänglichste und am besten verwaltete DnD-Bibliothek im React-Ökosystem ab 2025 ist.
Hier ist die vereinfachte Struktur:
// src/components/KanbanBoard.tsx
import { DndContext, DragOverlay } from '@dnd-kit/core';
import { useState } from 'react';
import { KanbanColumn } from './KanbanColumn';
import { DealCard } from './DealCard';
import { useDeals } from '../hooks/useDeals';
import { useRealtimeDeals } from '../hooks/useRealtimeDeals';
const STAGES = ['lead', 'contacted', 'proposal', 'negotiation', 'won', 'lost'];
const SOURCE_COLORS: Record<string, string> = {
referral: '#10b981',
organic: '#3b82f6',
outbound: '#f59e0b',
repeat: '#8b5cf6',
other: '#6b7280',
};
export function KanbanBoard() {
const { deals, moveDeal } = useDeals();
const [activeId, setActiveId] = useState<string | null>(null);
// Abonnieren Sie Echtzeit-Änderungen
useRealtimeDeals();
const handleDragEnd = async (event) => {
const { active, over } = event;
if (!over) return;
const dealId = active.id;
const newStage = over.data.current?.stage;
const newPosition = over.data.current?.position;
if (newStage) {
await moveDeal(dealId, newStage, newPosition);
}
setActiveId(null);
};
return (
<DndContext onDragStart={({ active }) => setActiveId(active.id)} onDragEnd={handleDragEnd}>
<div className="kanban-grid">
{STAGES.map((stage) => (
<KanbanColumn
key={stage}
stage={stage}
deals={deals.filter((d) => d.stage === stage)}
sourceColors={SOURCE_COLORS}
/>
))}
</div>
<DragOverlay>
{activeId ? <DealCard deal={deals.find((d) => d.id === activeId)} overlay /> : null}
</DragOverlay>
</DndContext>
);
}
Die Funktion moveDeal führt ein optimistisches Update durch — sie aktualisiert sofort den lokalen Status, sendet dann das Update an Supabase. Wenn die Datenbank-Aktualisierung fehlschlägt, wird sie zurückgerollt. Dies macht das Board sofortig.
const moveDeal = async (dealId: string, newStage: string, newPosition: number) => {
// Optimistisches Update
setDeals((prev) =>
prev.map((d) =>
d.id === dealId
? { ...d, stage: newStage, position: newPosition, stage_entered_at: new Date().toISOString() }
: d
)
);
const { error } = await supabase
.from('deals')
.update({
stage: newStage,
position: newPosition,
stage_entered_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
})
.eq('id', dealId);
if (error) {
// Zurückrollen — vom Server neu abrufen
await refreshDeals();
toast.error('Deal konnte nicht verschoben werden');
}
// Aktivität protokollieren
await supabase.from('activities').insert({
deal_id: dealId,
user_id: currentUser.id,
action: 'stage_change',
details: { from: previousStage, to: newStage },
});
};
Die Astro-Seite, die dies hostet, ist minimal:
---
// src/pages/board.astro
import Layout from '../layouts/App.astro';
import { KanbanBoard } from '../components/KanbanBoard';
import { getSession } from '../lib/auth';
const session = await getSession(Astro.request);
if (!session) return Astro.redirect('/login');
---
<Layout title="Deal Board">
<KanbanBoard client:load />
</Layout>
Diese client:load-Direktive leistet die schwere Arbeit. Das Layout, die Navigation und die Seiten-Shell sind alle serverbezogenes HTML. Das Kanban-Board selbst wird auf dem Client rehydriert. Dies bedeutet, dass das initiale Seiten-Laden schnell ist — der Browser erhält sofort HTML und das interaktive Board startet gleich danach.
Echtzeit-Updates mit Supabase Realtime
Dies war das Feature, das Supabase für dieses Projekt zum klaren Gewinner machte. Wenn ein Teammitglied einen Deal verschiebt, sehen die anderen Teammitglieder ihn in Echtzeit verschieben. Keine Aktualisierung erforderlich.
// src/hooks/useRealtimeDeals.ts
import { useEffect } from 'react';
import { supabase } from '../lib/supabase';
import { useDealsStore } from '../stores/deals';
export function useRealtimeDeals() {
const { updateDealLocally, addDealLocally, removeDealLocally } = useDealsStore();
useEffect(() => {
const channel = supabase
.channel('deals-changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'deals' },
(payload) => {
switch (payload.eventType) {
case 'UPDATE':
updateDealLocally(payload.new);
break;
case 'INSERT':
addDealLocally(payload.new);
break;
case 'DELETE':
removeDealLocally(payload.old.id);
break;
}
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
}
Ein Knackpunkt: Wenn SIE einen Deal verschieben, erhalten Sie Ihre eigene Änderung über das Echtzeit-Abonnement zurück. Wenn Sie nicht vorsichtig sind, führt dies zu einem visuellen Glitch, bei dem die Karte springt. Wir handhaben dies, indem wir optimistische Updates mit einem Zeitstempel kennzeichnen und Echtzeit-Events ignorieren, die neuliche lokale Änderungen entsprechen. Es sind ein paar zusätzliche Codezeilen, aber es macht die UX solide.
Authentifizierung und Row-Level Security
Da es sich um ein internes Tool handelt, ist die Auth einfach. Wir verwenden Supabase Auth mit E-Mail/Passwort. Drei Konten. Kein Sign-up-Flow — wir erstellten die Konten manuell im Supabase-Dashboard.
Row-Level Security ist, wo es interessant wird. Obwohl dies ein internes Tool ist, bedeutet RLS, dass unsere Datenbank nicht versehentlich Daten durchsickern lässt, selbst wenn wir den Anwendungscode vermasseln.
-- Nur authentifizierte Benutzer können Deals sehen
alter table deals enable row level security;
create policy "Authentifizierte Benutzer können alle Deals lesen"
on deals for select
to authenticated
using (true);
create policy "Authentifizierte Benutzer können Deals einfügen"
on deals for insert
to authenticated
with check (true);
create policy "Authentifizierte Benutzer können Deals aktualisieren"
on deals for update
to authenticated
using (true);
create policy "Authentifizierte Benutzer können Deals löschen"
on deals for delete
to authenticated
using (true);
Ja, diese Richtlinien sind nachgiebig — jeder authentifizierte Benutzer kann alles tun. Für ein dreiköpfiges Team ist das in Ordnung. Wenn wir jemals auf eine Größe wachsen, bei der wir rollenbasierte Berechtigungen brauchen, ist die RLS-Infrastruktur bereits vorhanden. Wir würden einfach die Richtlinien verschärfen.
Bereitstellung und Hosting-Kosten
Hier ist der spaßige Teil. Sprechen wir über Geld.
| Service | Plan | Monatliche Kosten |
|---|---|---|
| Supabase | Kostenlos-Tier | $0 |
| Vercel (Hosting Astro SSR) | Pro-Plan (hatten bereits) | $0 inkrementell |
| Domain | Subdomain der bestehenden Domain | $0 |
| Gesamt | $0/Monat |
Wir waren bereits auf Vercel Pro-Plan für Client-Projekte, daher kostet die Bereitstellung einer weiteren SSR-App uns nichts extra. Der kostenlose Tier von Supabase gibt uns 500MB Datenbank-Speicher, 50.000 monatlich aktive Benutzer (wir haben 3) und Echtzeit-Verbindungen. Wir verwenden vielleicht 1% der Kapazität des kostenlosen Tiers.
Vergleichen Sie das mit Monday.com:
| Monday.com | Unser benutzerdefiniertes CRM | |
|---|---|---|
| Monatliche Kosten | $48 (3 Plätze, Pro) | $0 |
| Jährliche Kosten | $576 | $0 |
| Build-Zeit | 0 Stunden | ~20 Stunden |
| Wartung | 0 Stunden/Monat | ~1 Stunde/Monat |
Bei unserer internen Stundenrate sind 20 Stunden Entwicklungszeit viel mehr wert als $576/Jahr. Aber die Mathematik verfehlt den Punkt. Wir bauten das teilweise, weil wir wollten, teilweise, weil es ein besseres Werkzeug für unseren spezifischen Arbeitsablauf ist, und teilweise, weil diese 20 Stunden uns Dinge lehrten, die wir seitdem bei Client-Projekten verwendet haben. Wir haben seitdem ähnliche Astro + Supabase-Architekturen für headless CMS-gestützte Anwendungen angewendet, die wir für Clients bauen.
Was wir anders machen würden
Es ist etwa vier Monate her, seit wir v1 gelauncht haben. Hier ist, was ich ändern würde:
Verwenden Sie Zustand von Anfang an
Wir begannen mit Reacts eingebautem useState und useContext für Zustandsverwaltung. Bis wir Echtzeit-Sync, optimistische Updates und Rollback-Logik hinzufügten, war der Zustandsverwaltungscode verwickelt. Wir migrierten nach zwei Wochen zu Zustand. Hätte dort starten sollen.
Suche früher hinzufügen
Wir bauten Suche nicht bis Woche drei, und diese drei Wochen des manuellen Durchsuchens von Spalten nach einem bestimmten Deal waren nervig. Eine einfache ilike-Abfrage auf Supabase hätte 30 Minuten zum Implementieren gedauert.
Tastaturkürzel
Haben das immer noch nicht hinzugefügt, aber wir wollen es. Drücken Sie N, um einen neuen Deal zu erstellen, / zum Suchen, 1-6, um nach Phase zu filtern. Kleine Dinge, die sich addieren, wenn Sie mehrmals täglich im Tool sind.
Bessere Mobile-Ansicht
Das Kanban-Board funktioniert technisch auf Mobiltelefonen. Aber sechs Spalten passen nicht auf einen Handy-Bildschirm. Wir brauchen eine Listenansicht für Mobilgeräte. Haben das nicht priorisiert, weil wir das CRM selten auf unseren Telefonen überprüfen, aber es wäre schön.
FAQ
Wie lange hat es gedauert, das CRM-Kanban-Board zu bauen?
Die erste nutzbare Version hat etwa 20 Stunden verteilt auf ein Wochenende und ein paar Abende gedauert. Das brachte uns das Kanban-Board, Deal-Details, Ziehen und Ablegen sowie grundlegende Auth. Wir haben wahrscheinlich seit der Zeit noch etwa 10 Stunden damit verbracht, Verbesserungen wie Suche, bessere Mobile-Stile und Bugfixes zu machen.
Warum Astro anstelle von Next.js für eine dynamische App?
Astros Island-Architektur bedeutet, dass die nicht-interaktiven Teile unserer App (Layout, Navigation, statische Seiten) null JavaScript liefern. Das Kanban-Board selbst ist eine React-Island, die beim Laden rehydriert. Für ein internes Tool, bei dem die interaktive Oberfläche auf eine Komponente fokussiert ist, ist dies ein großartiger Fit. Wir verwenden Next.js für Client-Projekte, wo die Interaktivität stärker über Seiten verteilt ist.
Reicht Supabase kostenlos-Tier wirklich für ein CRM aus?
Für ein kleines Team, absolut. Wir haben vielleicht 200 Deals, 150 Kontakte und ein paar tausend Aktivitätsprotokoll-Einträge. Das sind Kilobyte an Daten. Der kostenlose Tier von Supabase gibt Ihnen 500MB Speicher, den wir jahrelang nicht erreichen werden. Die Echtzeit-Verbindungen-Obergrenze ist auch großzügig — Sie erhalten bis zu 200 gleichzeitige Verbindungen im kostenlosen Plan.
Was ist mit Sicherungen?
Supabase beinhaltet tägliche Sicherungen im Pro-Plan ($25/Monat), aber wir sind im kostenlosen Tier. Wir führen eine wöchentliche pg_dump über einen Cron-Job auf einem $5/Monat VPS aus, den wir bereits hatten. Es ist nicht glamourös, aber es funktioniert. Wir haben auch einen Supabase-Projekt-Klon, den wir wiederherstellen können, wenn etwas schief geht.
Kann dieser Ansatz für ein Team größer als 3 Personen funktionieren?
Bis zu vielleicht 10-15 Personen denke ich, würde dies mit strafferen RLS-Richtlinien und etwas rollenbasierter UI-Logik funktionieren. Darüber hinaus würden Sie anfangen, Features wie Automationen, benutzerdefinierte Workflows und Berichte zu wollen, die ernsthafte Entwicklungsarbeit erfordern würden. An diesem Punkt macht ein dediziertes CRM-Tool mehr Sinn — nur vielleicht nicht Monday.com.
Wie ist die Leistung der Echtzeit-Sync?
Supabase Realtime verwendet WebSockets unter der Haube, und für unseren Use-Case (3 gleichzeitige Benutzer, gering-häufige Updates) ist es im Wesentlichen sofort. Wir gemessen die End-to-End-Latenz von einem Benutzer, der eine Karte zieht, bis ein anderer Benutzer das Update sieht: typischerweise 80-150ms. Das ist schneller, als wir wahrnehmen können.
Haben Sie Open-Source-CRM-Alternativen wie Twenty oder Folk in Betracht gezogen?
Wir schauten uns Twenty (das Open-Source-CRM, das 2024 gelauncht wurde) an und es ist beeindruckend. Aber es ist ein vollständiges CRM mit viel mehr Features, als wir brauchten, und Self-Hosting erfordert mehr Infrastruktur. Unser Ziel war es, genau das zu bauen, was wir brauchten und nichts mehr. Wenn Twenty existiert hätte, als wir anfingen, und hätte einen einfacheren Kanban-fokussierten Modus gehabt, hätten wir diesen Weg vielleicht genommen.
Würden Sie benutzerdefinierte interne Tools auch für Clients bauen?
Das haben wir tatsächlich getan. Mehrere Clients sind zu uns gekommen, nachdem sie über Tools wie Monday.com, Notion oder Airtable für spezifische Workflows hinausgewachsen sind. Wir bauen diese typischerweise mit Astro oder Next.js im Frontend und Supabase oder einem Headless CMS im Backend. Wenn das wie etwas klingt, das Sie brauchen, sollten wir sprechen.