Tasky – Project Management
Tasky is one of the most popular free project management templates on Bubble.io with strong community ratings. It offers projects, tasks with kanban boards, team assignment, and activity tracking. We rebuilt every page from scratch using Next.js 15 App Router, Supabase (Auth + PostgreSQL), shadcn/ui, and Tailwind CSS. The result is a Linear-quality project management tool that loads in under a second — compared to Bubble's 8–12 second load times.
Before & After
Switch between the live Bubble original and the Next.js rebuild — same screen, same data, wildly different performance.
Performance comparison
Down from 8–12s on Bubble
Down from $349/mo on Bubble
Your code, your infra — no vendor lock-in
What's included
- ✓Email/password auth (Supabase Auth) — login, signup, forgot password
- ✓Dashboard — KPI stat cards (projects, tasks, completed, in-progress) + recent activity feed
- ✓Projects — card grid with status badges, color coding, search + filter by status
- ✓Project Detail — full Kanban board (Todo / In Progress / Done) with task cards
- ✓Tasks — global task list with sort, filter by status/priority, assignee avatars, due dates
- ✓Team — team members list with role badges and project count
- ✓Settings — profile editing, avatar, theme toggle
- ✓Collapsible sidebar with icon+label nav and active indicator
- ✓Dark/light mode via next-themes with system default
- ✓Responsive design with mobile drawer sidebar
- ✓Supabase RLS policies on all tables
- ✓Activity logging for project/task actions
Real code
This isn't a mockup — it's the actual source. Click any tab to explore key files.
"color:#ff7b72">import { createClient } "color:#ff7b72">from "@/lib/supabase/server";
"color:#ff7b72">import "color:#ff7b72">type { Project, Task, ActivityLog } "color:#ff7b72">from "@/lib/supabase/database.types";
"color:#ff7b72">import { Card, CardContent, CardDescription, CardHeader, CardTitle } "color:#ff7b72">from "@/components/ui/card";
"color:#ff7b72">import { Badge } "color:#ff7b72">from "@/components/ui/badge";
"color:#ff7b72">import { Progress } "color:#ff7b72">from "@/components/ui/progress";
"color:#ff7b72">import {
FolderKanban, ListTodo, CheckCircle2, Clock, Plus, TrendingUp, Activity
} "color:#ff7b72">from "lucide-react";
"color:#ff7b72">import Link "color:#ff7b72">from "next/link";
"color:#ff7b72">import { formatDistanceToNow } "color:#ff7b72">from "date-fns";
"color:#ff7b72">async "color:#ff7b72">function getStats(supabase: Awaited<ReturnType<"color:#ff7b72">typeof createClient>>, userId: string) {
"color:#ff7b72">const [projectsResult, tasksResult, completedResult, inProgressResult] = "color:#ff7b72">await Promise.all([
supabase."color:#ff7b72">from("projects").select("id", { count: "exact" }).eq("owner_id", userId),
supabase."color:#ff7b72">from("tasks").select("id", { count: "exact" }),
supabase."color:#ff7b72">from("tasks").select("id", { count: "exact" }).eq("status", "done"),
supabase."color:#ff7b72">from("tasks").select("id", { count: "exact" }).eq("status", "in_progress"),
]);
"color:#ff7b72">return {
totalProjects: projectsResult.count || 0,
totalTasks: tasksResult.count || 0,
completedTasks: completedResult.count || 0,
inProgressTasks: inProgressResult.count || 0,
};
}
"color:#ff7b72">export "color:#ff7b72">default "color:#ff7b72">async "color:#ff7b72">function DashboardPage() {
"color:#ff7b72">const supabase = "color:#ff7b72">await createClient();
"color:#ff7b72">const { data: { user } } = "color:#ff7b72">await supabase.auth.getUser();
"color:#ff7b72">if (!user) "color:#ff7b72">return "color:#ff7b72">null;
"color:#ff7b72">const stats = "color:#ff7b72">await getStats(supabase, user.id);
"color:#ff7b72">const completionRate = stats.totalTasks > 0
? Math.round((stats.completedTasks / stats.totalTasks) * 100)
: 0;
"color:#ff7b72">return (
<div className="p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
<p className="text-muted-foreground">Your projects at a glance.</p>
</div>
<Link href="/projects/">new">
<Button><Plus className="mr-2 h-4 w-4" />New Project</Button>
</Link>
</div>
<div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
<StatCard title="Projects" value={stats.totalProjects} icon={FolderKanban} />
<StatCard title="Total Tasks" value={stats.totalTasks} icon={ListTodo} />
<StatCard title="Completed" value={stats.completedTasks} icon={CheckCircle2} />
<StatCard title="In Progress" value={stats.inProgressTasks} icon={Clock} />
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" /> Completion Rate
</CardTitle>
</CardHeader>
<CardContent>
<Progress value={completionRate} className="h-3" />
<p className="text-sm text-muted-foreground mt-2">{completionRate}% "color:#ff7b72">of tasks done</p>
</CardContent>
</Card>
</div>
);
}Ready to migrate your Bubble app?
We rebuild your specific Bubble app — not a generic template. Book a free 15-minute call to get a scope and fixed price.

