Overview
Cenprot brings together the electronic services of Brazil's protest notary offices. Resolve is where debtors look up and settle debts and fees, and where offices register debts, send notices and track cases. The goal was to replace multi-step bureaucratic flows with a direct journey.
Challenge
Two audiences with opposite needs share the same product. The office module is authenticated and exposes different features depending on each user's function. The public modules need to be simple and cheap to serve. The initial architecture used Next.js with SSR for the office areas and Vite elsewhere. Once the product was live, it became clear that part of the SSR cost wasn't paying off.
My role
- Led frontend development for the platform.
- Implemented role-based access control (RBAC) for the office module, modeled as capabilities.
- Reviewed the hybrid architecture and proposed consolidating on Vite. I discussed the proposal with the tech leads and backend, and the migration started while I was still on the project.
- Introduced TanStack Query (React Query) with caching and revalidation strategies.
- Took part in implementing and integrating the authentication flows with AWS Cognito and gov.br.
Architecture
Capability-based RBAC
Permissions came from the auth data and from the API for the logged-in user. Instead of asking which profile the user has, the UI asks what they can do, with something like can('financial-document:create'). That check was centralized and drove route access, module visibility, which actions were shown and which features were enabled, so no authorization logic was scattered across components. Enforcement stayed on the backend. The frontend's job was to show each user only what they were allowed to use.
Server state with TanStack Query
API calls moved to TanStack Query for caching and revalidation. That cut repeated requests between screens and made the app feel faster, without building a hand-rolled store for data that belongs to the server.
Different rendering per module
The office modules ran on Next.js with SSR, and the public modules ran as React + Vite SPAs. That hybrid was the starting point. Reassessing it was the next step.
Key decisions & trade-offs
Questioning SSR instead of defending it
Looking at the flows once the product was live, most were authenticated, API-driven and had no SEO needs. There, SSR added a server to run and processing that gave users nothing. I proposed consolidating on Vite, with static delivery through a CDN and simpler operations.
Keeping the hybrid during the transition
Rewriting everything at once would have stalled a product that was already live. We kept the hybrid and migrated piece by piece, accepting two stacks side by side for a while so delivery didn't stop.
Capabilities instead of role names
Checking role names is quicker to build, but it spreads business rules across the UI and breaks every time a new role appears. Capabilities require mapping permissions upfront. In return, roles can change without rewriting routes and components.
Results
- Launched with an authenticated module for notary offices and public modules for debtors.
- Migration to Vite started while I was there, aiming to drop server-side work that added no value and serve the app statically from a CDN.
- Centralized access rules: per-user capabilities, with no route or component rewrites when roles change.
- Fewer redundant requests and a snappier feel thanks to TanStack Query caching and revalidation.
Role
Senior Frontend Engineer · frontend technical lead
Tech stack
- React
- TypeScript
- Next.js
- Vite
- TanStack Query
- Tailwind CSS
- AWS Cognito








