2026Next.js 16TypeScriptTailwind CSS v4TanStack Query v5Framer Motion v12Zustand v5next-themesLucide ReactVitestTesting LibraryPlaywrightESLint 9PrettierHuskyGitHub Actions
What I Built
- Infinite scroll grid with TanStack Query useInfiniteQuery; type filter transparently swaps to a regular useQuery when PokéAPI returns a non-paginated full-type list, without changes to the UI layer
- Client-side search: all ~1 300 Pokémon names fetched once (staleTime: Infinity) and filtered with a 300ms debounce — eliminates per-keystroke API round-trips
- Static generation with generateStaticParams for the first 151 detail pages; server-side fetch cache set to revalidate: 86400 for the rest
- Tailwind CSS v4 with the new @theme CSS token API: design tokens defined in :root / .dark and mapped to utility classes via @theme inline — no tailwind.config.js needed
- Side-by-side Pokémon comparison (Zustand v5 + Persist middleware) reading from the existing TanStack Query cache — no duplicate network requests
- Promise.allSettled on batch detail fetches: individual PokéAPI 404s for non-standard forms are swallowed without aborting the list render
- Dark mode with next-themes: system preference honoured on first server render, user override persisted in localStorage, zero FOUC
- Full quality pipeline: Vitest unit and integration tests, Playwright E2E, ESLint 9, Prettier, Husky pre-commit hooks, GitHub Actions CI
How It Works
- 1The list page runs useInfiniteQuery against PokéAPI, fetching 24 items at a time and batch-resolving each page's details in parallel with Promise.allSettled.
- 2Activating a type filter switches the data source to /type/{name} and replaces infinite scroll with a regular grid — the URL search param drives both the query key and the UI state.
- 3The search dropdown fetches all ~1 300 names once on mount and filters client-side with a 300ms debounce; results are displayed as a keyboard-navigable suggestions list.
- 4The comparison view reads both Pokémon from the existing Query cache — if a detail page was already visited, no new request is made.
- 5On the detail page, the evolution chain is resolved recursively from the species endpoint; stat bars animate in on mount with Framer Motion.