Dev notes
Making a search input interactive without converting the whole page to a client component — pushing the client boundary as low as possible.
The hub page is a server component — it calls await auth0.getSession() to get the user session. Server components can't use useState or handle user input, so the search input was just decoration. Making it interactive required something to become a client component.
Converting the whole page to a client component would mean losing server-side auth — auth0.getSession() only works in server components, where it can read cookies from the request. Instead, only the search bar and thread list were extracted into a ThreadList client component.
The page itself stays server-rendered: it fetches the session, reads the user name and email, and renders the layout. It passes the threads array down as a prop. The client component handles interactive filtering. The principle: push client boundaries as low as possible — only the leaf that needs interactivity gets "use client".
Simple .filter() on every keystroke — case-insensitive .includes() check against three fields: name, href, and preview. With 4 threads this is instant. No debouncing, no fuzzy matching, no external library needed.
Server components are the default in Next.js for a reason — they don't ship JS to the browser, they can access server-only APIs directly, and they render on first paint without hydration delay. Making the whole page a client component would have been simpler to write, but it's a bad pattern to establish. The right amount of complexity here is the minimum needed: .filter() and .includes() over 4 items. No more.