Next.js + Supabase: How to Use service_role Without Exposing It
The service_role key is powerful. Used correctly, it simplifies a backend. Put in the wrong place, it puts the whole project at risk.
The service_role key simplifies many things in a Supabase project. It allows admin operations, server scripts, data migrations, or back-office tasks. But it has a non-negotiable rule: it must never leave in the browser.
Supabase says it very clearly in its API keys documentation: never expose service_role publicly, and never use it in a browser, even locally.
Why is this key so sensitive
The service_role key gives elevated privileges on your project. It is made for code controlled by the developer:
- server route handlers
- planned jobs
- administration scripts
- back office with strict access controls
If this key arrives in the front, in a bundle or in a public repo, the problem is not "potentially annoying". The problem is immediate. You are giving way too much access to your base and resources.
The most common pitfall with Next.js
The classic trap looks like this:
- we want to take an admin action quickly
- we reuse the same Supabase client everywhere
- we put the key
service_rolein a poorly placed environment variable - we import this client into a component which ends up on the client side
In Next.js, the server/client boundary is very important. A file marked with "use client" or imported from a client component must never embed logic that depends on a server secret.
The good principle is simple: the key service_role only lives in executed server code.
The right schema in a Next.js project
In practice, you have two families of clients:
1. The public client or user
It uses:
- the public Supabase URL
- a
anonor publishable key
It is used for the normal route:
- public reading
- connected user actions
- everything that must respect the RLS policies of the real user
2. The admin client or server
It uses:
- the Supabase URL
- the key
service_role
It is only used in controlled locations:
- road handlers
- server actions if they really remain on the server side
- maintenance scripts
- administration tools with upstream verification
An example of healthy separation
The clean pattern looks like this:
// client public
createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!)
// client serveur createClient(process.env.NEXTPUBLICSUPABASEURL!, process.env.SUPABASESERVICEROLEKEY!) ```
And above all:
- the public client can be imported on the browser side
- the client
service_rolemust remain in a server module
In a Next.js project, this means in practice:
- no import into a component
"use client" - no use in an action triggered from the front without clear server guard
- no variable
NEXTPUBLIC...for a server secret
The costly mistake
There is a very simple but very serious error: prefixing a secret with NEXTPUBLIC.
In Next.js, everything that starts with NEXTPUBLIC is designed to be available on the client side. So if you put your service_role in there, you're changing a server secret into potentially exposed information.
If you have any doubt, check your environment variables and imports right away.
What to do for a product rating admin action
If your product needs admin action, the correct method is not to give the key to the browser. The correct method is:
- the user calls a server route
- this route verifies that he has the right to perform the action
- the server uses
service_roleto execute the sensitive operation - the response returned to the customer remains minimal
This diagram keeps critical logic in a place that you control.
The case of modern Supabase keys
The Supabase documentation today recommends switching to publishable and secret keys when possible. But the security principle does not change:
- public key = possible front
- secret or privileged key = server only
In other words, even if your project evolves towards the new keys, the discipline remains the same.
How to audit your project quickly
Make this mini checklist:
- Search for
SUPABASESERVICEROLE_KEYin the repo. - Check that the files using it are on the server side.
- Verify that no secret variable is prefixed with
NEXTPUBLIC. - Check Supabase client imports in
"use client"components. - Test that a sensitive action always goes through an upstream server check.
If one of these steps is not clean, it must be corrected before adding other features.
A good team reflex
The best safeguard is to have two clearly named modules:
- a public client
- an admin client
And to set a very simple team rule: the admin client is never imported into client code.
This rule is more useful than a long safety document that no one reads.
What to remember
service_role is a server tool, not a front-end tool.
If you respect the server/client boundary in Next.js, this key becomes very useful. If you confuse it, you open a severe risk to the database, accounts and project data.
The right architecture is not complicated: a public client for the user, an admin client for the server, and no confusion between the two.
Official sources
Keep reading on the same topic
These links reinforce the blog's topical cluster and help search engines understand the article's primary subject.
