Full-Stack Web Application

ProjectHub

A production-grade project management and collaboration platform with real-time feeds, GitHub integration, streaks, and a custom dark theme design system called Landa.

📅 2026 ⚡ Functional — all major features implemented 📈 TypeScript, Next.js 14, Fastify, PostgreSQL, Redis, Socket.io 🔗 GitHub

01 The Problem

Modern software teams juggle an ever-growing stack of collaboration tools — one for tasks, another for code, a third for documentation, and yet another for real-time communication. The result is context-switching fatigue: developers spend as much time navigating between tools as they do actually building software. Existing project management platforms are either too heavy (Jira), too fragmented (spreadsheets + Slack + Notion), or too limited in their integration with the development workflow.

ProjectHub was conceived as a unified collaboration dashboard that brings project management, real-time activity feeds, GitHub integration, contributor analytics, and team communication into a single, coherent experience. Built around the Landa design system — a custom dark theme with a distinctive teal (#09bc8a) accent and deep dark (#010907) backgrounds — it aims to prove that a full-stack application can be both feature-rich and delightful to use.

The core challenge was real-time data synchronisation across multiple users viewing and editing the same projects simultaneously, combined with the need for persistent state (tasks, projects, streaks) that survives page reloads and session boundaries. The solution needed to handle OAuth from multiple providers, role-based access control, and a GitHub-style contribution heatmap — all while staying performant on modest hardware.

02 Architecture

ProjectHub follows a three-layer architecture with clear separation of concerns. The Next.js 14 App Router handles server-side rendering and routing on the frontend, Fastify serves a REST API with Zod-validated schemas on the backend, and Socket.io bridges the two for real-time event propagation. PostgreSQL is the primary data store, with Redis handling caching, session management, and pub/sub for cross-instance event broadcasting.

Frontend (Next.js 14 App Router)
├── Dashboard + Stats
├── Project View + Tasks + Members
├── Activity Feed
├── Contribution Graph + Streaks
├── Resources & Links
├── Notifications
└── Settings
    │ HTTP + WebSocket
    ▼
Backend (Fastify + TypeScript)
├── REST API (Zod validation)
├── JWT + OAuth Auth (Google, GitHub)
└── Socket.io (real-time events)
    │
    ▼
Database Layer
├── PostgreSQL (primary data)
└── Redis (caching, sessions)
⚡ Design Decision

Using both TanStack Query and Zustand was deliberate: TanStack Query manages all server-state (projects, tasks, feeds) with automatic invalidation and prefetching, while Zustand holds only ephemeral UI state (sidebar open, active filter, compose-draft content). This keeps the architecture clean: the server is the single source of truth, and the client never caches stale data beyond what the UX requires for responsiveness.

03 Tech Stack

Every technology was chosen for a specific reason — not because it was trendy, but because it solved a real problem in the architecture. The stack spans the full application layer, from database to design system.

Technology Role
TypeScript Full-stack type safety — shared types between frontend and backend
Next.js 14 App Router SSR, file-based routing, server components for dashboard pages
Fastify Backend API — fast, schema-first with Zod validation
PostgreSQL Primary data store — projects, tasks, users, activity log
Redis Caching, session store, pub/sub for Socket.io scaling
Socket.io Real-time bidirectional events — activity feed, notifications
TanStack Query Server-state management — caching, prefetching, optimistic updates
Zustand Client-state management — lightweight, hook-based store
Tailwind CSS Utility-first styling — fast iteration, consistent design tokens
Framer Motion Page transitions, skeleton loaders, streak flame animations
JWT + OAuth Authentication — JWT sessions + Google & GitHub OAuth

04 Key Challenges

📺 1. Real-Time Collaboration Without Event Spam

When multiple users view and edit the same project simultaneously, every task update, member change, or comment creates a Socket.io event. Without careful event scoping, a single user's action could trigger dozens of redundant broadcasts — flooding the client and degrading UX. The solution was a room-based subscription model: each project is a Socket.io room, and each event includes a scope field (e.g., project:task:update). Clients subscribe only to the scopes they need, and the server deduplicates identical events within a 500 ms window before broadcasting. This reduced event volume by roughly 70% during peak usage.

🚀 Implementation Detail

The deduplication window uses a Map<string, { payload, timer }> keyed by projectId:scope. When a new event arrives, if an identical payload already exists for that scope, the timer is reset. Only the last event in each window is actually emitted. This means rapid toggles (e.g., assigning and unassigning a member) collapse to a single broadcast with the final state.

🌎 2. Cross-Timezone Streak Tracking

The contribution heatmap and streak system needed to handle users spread across UTC, IST, EST, and beyond. What counts as "today" for a user in New York is still "yesterday" for a user in Mumbai. A naive UTC-based approach would break streaks whenever a user crossed midnight in their local timezone.

The solution stores every contribution event with the user's configured timezone offset (stored in their profile preferences). The streak engine queries contributions grouped by the user's local day boundary, not UTC midnight. When a user changes their timezone, existing contributions are re-mapped to their new local calendar — this is a one-time background job that runs asynchronously. The heatmap renderer uses the same logic, ensuring the contribution graph always reflects the user's chosen timezone.

🔒 3. Three-Provider OAuth with Account Linking

Supporting Google OAuth, GitHub OAuth, and JWT-based credentials simultaneously introduces edge cases: a user might sign up with Google, then try to log in with GitHub using the same email. Without account linking, this creates duplicate accounts. With naive linking, it opens security holes (e.g., claiming another user's account by registering their email with a different provider).

ProjectHub implements a verified email linking flow: when a provider returns an email that already exists in the database, the user must confirm ownership via a one-time code sent to that email before the accounts are linked. This prevents unauthorised account claiming while allowing legitimate multi-provider access. Session consistency across providers is maintained via a shared JWT that encodes the user's primary ID and linked provider IDs — regardless of which provider they used to authenticate, the session token is the same.

📊 4. Dashboard Performance Under Load

The dashboard page loads projects, tasks, notifications, the activity feed, and streak data simultaneously. Without optimisation, this meant 5+ sequential API calls and visible loading jank. The solution combined two strategies:

Together these eliminated N+1 query patterns entirely and made the dashboard feel instant even on slow connections.

Code Highlight

The Socket.io event bus handles real-time collaboration — scoping notifications to the right users while avoiding spam during rapid changes.

io.on("connection", (socket) => {
  // Join a project room — scoped notifications per project
  socket.on("join_project", (projectId) => {
    socket.join(`project:${projectId}`);
  });

  // Task update → broadcast to room (not all users)
  socket.on("task_updated", (task) => {
    io.to(`project:${task.projectId}`).emit("task_changed", {
      taskId: task.id,
      status: task.status,
      assignee: task.assigneeId,
      updatedBy: socket.data.userId,
    });
  });

  // Activity feed entry (debounced server-side)
  socket.on("log_activity", (entry) => {
    debouncedActivityLog(entry, socket.data.userId);
  });
});

05 Results

3 Auth Providers
Google + GitHub + JWT
Real-Time Socket.io
Activity feed + notifications
Tasks Priority + Due Dates
Member assignment
Streaks Consecutive Day Tracking
Flame indicators

ProjectHub delivers a fully functional collaboration platform with every major feature implemented and working. The Landa design system provides a cohesive visual language across the entire application.

06 What I Learned

Building a real-time collaboration platform from scratch — across the entire stack — taught me as much about system design as it did about the human dynamics of collaborative software. Three lessons stand out:

📚 Lesson 1

State management is about boundaries, not tools. The decision to split state between TanStack Query (server) and Zustand (client) forced a clear architectural contract: what lives on the server is the source of truth; what lives on the client is ephemeral. This boundary made debugging trivial — if UI state was wrong, it was Zustand. If data was stale, it was TanStack Query's cache invalidation. Most state-management debates dissolve when you draw a clean line between server-owned and client-owned state.

📚 Lesson 2

Real-time adds an order of magnitude of complexity. Adding Socket.io to a REST API isn't just "also send a WebSocket event." You now have to reason about event ordering, deduplication, reconnection semantics, missed-message recovery, and cross-instance broadcasting (Redis pub/sub became necessary the moment we considered horizontal scaling). The 500 ms deduplication window was a simple heuristic that solved a hard problem — sometimes the best solutions are the ones that reduce surface area rather than increase it.

📚 Lesson 3

Timezone-aware features should be built from day one. Retrofitting timezone support into the streak engine was painful — it required a data migration to store timezone offsets on every event, a background job to re-map historical contributions, and careful testing across multiple timezone boundaries. Had we stored the user's timezone offset alongside each contribution from the start (or better yet, stored UTC timestamps and derived local dates at query time), the entire streak feature would have been simpler and more robust. Timezone is not an edge case; it's a first-class dimension of any user-facing calendar feature.