Built a Pokédex with Next.js and Headless Drupal

Pokedex

Building a Pokédex with Next.js 14 and Headless Drupal

Yes, PokeAPI exists. Yes, I could've just called it directly and been done in an afternoon. That's not the point.

This project actually started about a year ago — not as a decoupled app, but as a traditional Drupal site. I built a custom module to sync data from PokeAPI directly into Drupal, then built a custom theme on top to display everything: Pokémon details, evolutions, generations, legendary and mythical status. It worked, and I was happy with it at the time.

But I wanted to push it further. So I rebuilt the frontend from scratch using Next.js, keeping Drupal as the data layer and turning it into a proper headless setup. Same content, different architecture — and a much more interesting problem to work through.

 

From Coupled to Decoupled

In the original version, Drupal handled everything, from content to rendering. The custom sync module would pull from PokeAPI and populate Drupal nodes automatically, and the custom theme would render it all server-side. Simple, effective.

The rewrite kept the Drupal backend intact. The sync module still runs, and the content model is the same. What changed is that the theme is gone — Drupal now just exposes data through JSON:API, and Next.js takes it from there. It's a cleaner separation, and it opens up things like React interactivity, fine-grained caching control, and a proper auth layer that the old setup couldn't really support.

You can check out the Drupal sync module here: https://github.com/vinicius-o-souza/pokemon_api

 

Building with Claude Code

One thing worth being upfront about: I used Claude Code throughout this project, and it made a real difference.

On the Next.js side, Claude Code helped me build the entire application.

On the Drupal side, I used it to modernize the sync module. The original code worked but was written against older Drupal conventions. Claude Code helped me refactor it to Drupal 11 standards — migrating to OOP hooks, replacing annotations with PHP attributes, and making the code cleaner and more readable.

I also wrote .md files to give Claude Code context about the project — how it's structured, what conventions to follow, what to avoid. That's something I'd recommend regardless of the AI tool you're using. The more context you give it, the less time you spend correcting it.

 

Structuring Pokémon Data in Drupal

Pokémon data isn't flat — it has relationships — so I leaned into Drupal's content modeling rather than flattening everything into a single type.

  • Stats are stored as paragraphs (embedded entities), not plain fields
  • Types are taxonomy terms, referenced from each Pokémon node
  • Evolution chains are self-referential node relationships — a Pokémon references another Pokémon as its evolution

All of this gets exposed through JSON:API. The frontend parses those nested, relational responses and maps them into clean TypeScript models. It's the kind of data layer work that a direct PokeAPI call would completely skip over.

 

Authentication: OAuth 2.0 with PKCE and Token Refresh

Drupal's Simple OAuth module handles authentication on the CMS side. On the frontend, I wrote a custom NextAuth.js provider to integrate with it — implementing the full PKCE flow rather than relying on a simpler setup.

 

The Stack

  • Frontend: Next.js 14 (App Router), React 18, TypeScript, Tailwind CSS
  • Auth: NextAuth.js with a custom OAuth 2.0 provider
  • CMS: Headless Drupal exposing data via JSON:API
  • Deployment: Vercel with Analytics + Speed Insights

 

What I Got Out of It

The original Drupal site was a good project. The rewrite is a better one.

Using Claude Code changed how I work on side projects. It's not about writing less code — it's about spending less time on the parts that are just mechanical and more time on the parts that actually require decisions.

The Next.js application is open source — you can find the repository here: https://github.com/vinicius-o-souza/pokedex-nextjs

You can also check out the live version here: https://pokedex-nextjs-vercel-amber.vercel.app/

More insights