ChatApp is a Next.js 16 messenger client for private 1:1 threads and groups — not a public feed. It talks to a hosted assignment backend over REST and Socket.io. Sign in with a name and a Bangladesh (+880) phone number; a new number registers, a known number returns. There is no password and no local mock chat API.
The product splits marketing and the signed-in workspace. The landing page uses the same graphite + emerald system as the inbox so the pitch and the app feel like one surface. After login, ChatShell restores the JWT via GET /api/auth/me, then shows an inbox with All / Unread / Groups filters, Ctrl+K jump-to, direct threads, and groups with admin/member roles.
TanStack Query owns server cache; Zustand owns session, unread, dialogs, and a local user directory because list payloads often return participant ids with no names. Sends go through POST /api/messages with an optimistic bubble; Socket.io is inbound only (message:new, conversation:updated) so validation and errors stay on one write path. The live landing scores 99 performance, 97 accessibility, and 100 SEO on desktop PageSpeed Insights.
The Challenge
Client Objectives
Ship a working chat product against an existing, under-specified API — not a mock database.
Support phone sign-in, session restore, direct threads, and groups with roles.
Deliver messages live over Socket.io without stealing scroll on the open thread.
Add a creative landing page that matches the messenger’s visual system.
Core Challenges
Mapping an OpenAPI spec that omits most response bodies onto a complete product UI.
Restoring JWT sessions without flashing login on every reload.
Showing human names when conversation lists return ids only.
Keeping send errors, optimistic bubbles, and socket echoes in one write path.
My Solution
I built ChatApp as a frontend-only Next.js client against the hosted REST + Socket.io API. Auth starts as restoring until GET /api/auth/me finishes. I used TanStack Query for conversations, paginated messages, and people search (600ms debounce), Zustand for session and a user-directory cache, and REST POST /api/messages for sends with Socket.io inbound only. Defensive normalizers unwrap { data } envelopes, id-or-object senders, and pagination without a usable cursor. I did not fake attachments, presence, or extra backend routes.
Key Highlights
Phone sign-in with name + Bangladesh +880 number, JWT in localStorage, and session restore — no separate register screen and no password.
Inbox with All / Unread / Groups filters, local search, Ctrl+K jump-to, and a two-pane ChatShell that becomes list-or-thread below md instead of two squeezed columns.
Direct threads from people search; groups with create, rename, add, promote admin, remove, and leave — including Manage group for admins.
Optimistic send on REST; Socket.io message:new replaces the pending bubble; unread badges light other threads instead of jumping the open conversation; reconnect refetches gaps.
Landing and product share one graphite–emerald system (Geist, Framer Reveal, prefers-reduced-motion). Desktop PageSpeed on / : 99 performance, 97 accessibility, 100 best practices and SEO.