presque.cool
Back to projects
pokécodex icon

pokécodex

Oct 2018 Site
Visit project

An interactive Pokédex built on a static mirror of PokéAPI. Search, filters, evolution chains — fully offline.


Le pitch

Un Pokédex interactif : 1000+ Pokémon à parcourir, chercher, filtrer par type ou génération. Fiche détaillée avec stats, chaîne d’évolution, compatibilité de types et capacités. Entièrement offline — les données PokéAPI sont servies en local, sans aucun appel réseau à l’exécution.

Essayer : presque.cool/pokecodex/

Période Oct 2018 → Mars 2026
Stack
React TypeScript Tailwind CSS PWA
Statut ✓ En production

Pourquoi ce projet

Trois raisons qui se renforcent.

Première raison : PokéAPI est l’une des meilleures APIs publiques pour apprendre à consommer une vraie source de données structurée. Elle est riche, bien documentée, avec de la pagination, des ressources imbriquées, des relations entre entités (types, évolutions, générations). Un bon terrain d’entraînement.

Deuxième raison : Pokémon. Pas une obsession, mais une affection ancienne — suffisamment pour que l’exercice reste motivant sur la durée. Et la franchise fête ses 30 ans en 2026 : un bon moment pour ressortir le projet du tiroir.

Troisième raison : le défi technique. Comment construire une app qui dépend d’une API externe sans jamais en dépendre à l’exécution ? La question a conduit à toute l’architecture du projet.


Deux versions

Prototype jQuery (octobre 2018) : Premier Pokédex, jQuery. Appels directs à l’API en live, interface basique. Les données couvraient les générations 1 à 7 — les 809 Pokémon connus à l’époque.

Refonte React (mars 2026) : Réécriture complète de l’interface et de l’architecture, avec l’ajout des générations 8 et 9 (Épée/Bouclier et Écarlate/Violet). React, TypeScript strict, Tailwind v4, Zustand, PWA. Et une décision d’architecture qui change tout : les données PokéAPI sont téléchargées au moment du build et servies en statique depuis /api/v2/. L’app ne fait plus aucun appel externe.


Le miroir statique

La structure de PokéAPI est reproduite localement à l’identique — api/v2/pokemon/1/index.json, api/v2/type/10/index.json, etc. Au build, les données sont en place. Au runtime, les “appels API” sont en réalité des requêtes vers des fichiers statiques locaux.

Le service worker prend ensuite le relais : il met en cache ces fichiers JSON et les sprites (servis depuis GitHub). Une fois chargée, l’app fonctionne sans connexion.

Résultat : aucune dépendance à l’état de pokeapi.co, aucune limite de rate, temps de réponse constants, PWA offline complète. Contrepartie : 399 MB de données statiques dans le build.


Les filtres

Le système de filtres combinés est l’autre partie intéressante du projet. Filtrer par type sur 1000+ Pokémon implique de gérer les Pokémon double-type correctement — chercher “Eau + Vol” doit retourner les Pokémon qui ont ces deux types simultanément, pas l’union des deux listes.

Tout se fait côté client, sans index serveur. Les données par type et par génération sont pré-chargées, l’intersection se calcule en mémoire. Le défilement infini (intersection observer) gère l’affichage progressif sans bloquer le fil principal.


Stack technique

  • React + TypeScript strict — dernières versions, zero any
  • Vite + SWC
  • Zustand — state management des filtres, sélection, settings persistés
  • Tailwind CSS — palette rouge/orange Pokéball en OKLCh
  • PWA — installable, offline
  • Compression — Brotli + Gzip sur les assets
  • i18n maison — français/anglais, clés type-safe
  • CSP — Content-Security-Policy avec hashes auto-générés

Évolutions

DateVersionDescription
Oct 20181.0Prototype jQuery — appels API live, Gen 1 à 7
Mars 20262.0Réécriture React — miroir statique PokéAPI, filtres combinés, PWA offline

Bilan

Ce que j’ai appris :

  • Travailler avec une API publique bien conçue apprend autant que d’en construire une. La pagination, les ressources liées (espèce → évolution → formes) et les relations entre entités obligent à modéliser proprement des données complexes.
  • Le miroir statique est une contrainte utile. Se passer de l’API live force à distinguer les données réellement nécessaires du reste, et à penser l’app comme un système autonome.

Ce que je referais pareil :

  • Refaire le choix du miroir local de PokéAPI. L’app gagne en fiabilité, en vitesse perçue et en usage offline.
  • Garder les filtres combinés côté client. C’est la partie la plus intéressante du projet à l’usage comme à l’implémentation.

Ce que je changerais :

  • Je chercherais à réduire le poids du build. Les 399 MB de données statiques sont acceptables pour l’objectif, mais restent le principal coût de cette architecture.
  • Je formaliserais plus tôt les scripts d’extraction et de normalisation des données. Le miroir marche mieux quand sa chaîne de génération est pensée comme un produit à part entière.

The pitch

An interactive Pokédex: 1000+ Pokémon to browse, search, and filter by type or generation. Detailed pages with stats, evolution chains, type matchups, and abilities. Fully offline — PokéAPI data is served locally, with no network calls at runtime.

Try it: presque.cool/pokecodex/

Period Oct 2018 → Mar 2026
Stack
React TypeScript Tailwind CSS PWA
Status ✓ In production

Why this project

Three reasons that reinforce each other.

First: PokéAPI is one of the best public APIs to practice real structured data consumption. It’s rich, well-documented, with pagination, nested resources, and relationships between entities (types, evolutions, generations). A solid training ground.

Second: Pokémon. Not an obsession, but a long-standing fondness — enough to keep the exercise motivating. The franchise turns 30 in 2026: a good reason to dust the project off.

Third: the technical challenge. How do you build an app that depends on an external API without depending on it at runtime? That question drove the entire architecture.


Two versions

jQuery prototype (October 2018): First Pokédex, built with jQuery. Live API calls, basic UI. Data covered generations 1 to 7 — the 809 Pokémon known at the time.

React version (March 2026): Full rewrite. React, strict TypeScript, Tailwind v4, Zustand, PWA. And an architectural decision that changes everything: PokéAPI data is downloaded at build time and served as static files from /api/v2/. The app makes no external calls at runtime.


The static mirror

PokéAPI’s URL structure is replicated locally — api/v2/pokemon/1/index.json, api/v2/type/10/index.json, and so on. At build time, the data is in place. At runtime, “API calls” are actually requests to local static files.

The service worker handles the rest: it caches these JSON files and the Pokémon sprites (served from GitHub). Once loaded, the app works without a connection.

The result: no dependency on pokeapi.co’s uptime, no rate limits, consistent response times, full offline PWA. The trade-off: 399 MB of static data in the build.


The filters

The combined filter system is the other interesting part of the project. Filtering by type across 1000+ Pokémon means handling dual-type Pokémon correctly — searching “Water + Flying” should return Pokémon that have both types simultaneously, not the union of two lists.

Everything runs client-side, without a server index. Type and generation data is pre-loaded; intersection logic runs in memory. Infinite scroll (intersection observer) handles progressive rendering without blocking the main thread.


Tech stack

  • React + TypeScript strict — latest versions, zero any
  • Vite + SWC
  • Zustand — filter state, selection, persisted settings
  • Tailwind CSS — Pokéball red/orange palette in OKLCh
  • PWA — installable, offline
  • Compression — Brotli + Gzip on assets
  • i18n custom — French/English, type-safe keys
  • CSP — Content-Security-Policy with auto-generated hashes

Timeline

DateVersionDescription
Oct 20181.0jQuery prototype — live API calls, Gen 1 to 7
Mar 20262.0React rewrite — static PokéAPI mirror, combined filters, offline PWA

Takeaways

What I learned:

  • Working with a well-designed public API teaches as much as building one. Pagination, linked resources (species → evolution chain → forms), and entity relationships force you to model complex data cleanly.
  • The static mirror is a useful constraint. Giving up the live API forces you to separate what the app truly needs from everything else, and to think of the product as a self-contained system.

What I’d do the same:

  • I’d make the same call on the local PokéAPI mirror. The app gains reliability, perceived speed, and real offline use.
  • I’d keep the combined client-side filters. They’re the most interesting part of the project both in use and in implementation.

What I’d change:

  • I’d try to reduce the build weight. The 399 MB of static data are acceptable for the goal, but they remain the main cost of this architecture.
  • I’d formalize the extraction and normalization scripts earlier. The mirror works best when its data pipeline is treated as a product of its own.

Settings

Language
Theme
Privacy Policy