How to handle feature and permission-based authorization in Next.js without delaying initial render?
I’m building a multi-tenant SaaS application with Django Ninja as the backend and Next.js as the frontend. I’m running into a problem around handling RBAC permissions and org-level feature entitlements without causing bad UI behaviour.
Current setup
- The app has a pre-defined set of permissions and features.
- Each organization can:
- Create their own RBAC roles for members.
- Define their own plans (feature bundles) for members.
- Hierarchy looks like: Platform → Org → Members.
Problem
For RBAC, I first tried fetching permissions/features from an API after login. But in Next.js, this caused a UX issue:
- On initial render, the UI displayed all components/features.
- Only after the API call resolved (or a manual refresh), the UI updated to hide restricted components.
- This led to a flicker effect where users briefly saw components they shouldn’t.
To avoid this for RBAC, I moved the permissions into the JWT, so the frontend can instantly check them at page load. This works well, but I’m unsure if I should do the same for feature entitlements (since those are org-level and can change dynamically).
Question
- Is it acceptable to also put feature entitlements in the JWT (and refresh the token on org/plan changes)?
- Or is there a better pattern for preloading entitlements in Next.js so components render correctly on first load without flicker?
- How do people usually handle this combination of RBAC (user-level) and feature flags (org-level) in a multi-tenant SaaS?
Key constraints
- The UI should render correctly on first load (no flicker).
- I don’t want to bloat JWTs unnecessarily.
- I follow “trust but verify” → JWT is convenient for instant checks, but the backend still validates entitlements.
tldr: use SSR.
- Pages router: getServerSideProps If it's only a couple of pages, and getInitialProps if you need it on every page
- App router: I believe you can use layouts to fetch data and pass it on to the rest of the website; they also can be nested, and you could implement only a part of the website on the app/ router, leaving the rest in pages/, can be a bit tricky to setup though, watch out cause it really likes to skip rerendering it
This may not be the cleanest solution, but at least it should work.
I assume that at some point you will eventually load the user from the backend, to display name, avatar, etc. So why not preload it on the backend (NextJS) during the SSR phase, so requests can be cached, UI will be prerendered, no need to clutter JWT with additional data. About authorization, you can just read the client cookie in the SSR handler and pass it on to the backend in the Authorization header. If needed, a middleware on the server can convert it to a cookie (at the end, it's just another header), so you won't even have to change any backend auth for that.
Another plus of this method is that if you host your backend and frontend together on one machine, NextJS can talk directly to your backend, so fetching the user server-to-server will be even faster than making this request from the frontend.
Finally you if you use OpenApiGenerator, you can modify the client in such a way that it automatically chooses the right address (https://api.yourAwesomeProject.com or http://api_container_name:5000/) for the server based on the presence of a window object and accepts an optional argument for client cookies to forward when needed.
Notes:
- If using getInitialProps, beware that when the user navigates between pages using NextLink or back/forward buttons (in browser), it will run on the frontend, inside of it you can check if
window
is undefined to distinguish between scenarios - useful links about app router prerendering , layouts , fetching data
- Such global info as user is really convinient to have in a ReactContext
- If you want to update user later on without refreshing the page you can combine ReactContext with UseSWR, it accepts initialValue argument (you pass it from SSR) and can be configured to refetch user on certaintTiggers, I would strongly advise to create a hook useUser based on ReactContext and SSR that would expose any methods assosiated with modifing user. swr docs