A non-proxied route is not always bad.
Calling a public image CDN from the browser is normal. Loading a public analytics script is normal if consent rules are handled.
The problem is calling privileged third-party APIs directly from client-side code.
If the browser needs a secret to make the call, the call should not be in the browser.
The unsafe shape
Unsafe:
Browser -> third-party API with privileged key
Examples:
- browser calls OpenAI with a secret API key
- browser sends email through an email provider key
- browser creates Stripe objects with a secret key
- browser writes to object storage with broad permissions
- browser calls an internal admin endpoint directly
- browser talks to a database with a service role key
If a user can see the request, copy it, change it or replay it, you need server-side controls.
The safer shape
Safer:
Browser -> your server route -> third-party API
Your server route can:
- keep secrets server-side
- validate input
- authenticate the user
- check authorization
- apply rate limits
- enforce payment or plan limits
- log safely
- normalize third-party errors
- return only the data the UI needs
That is what a proxy route is for.
Find browser-side calls
Search:
rg "fetch\\(" app components lib
rg "api\\.openai|api\\.anthropic|stripe|maps.googleapis|resend|sendgrid"
Then ask:
Find every third-party API call in this app.
For each call, tell me:
- whether it runs in the browser or on the server
- whether it uses a secret or privileged token
- whether user input reaches the provider
- whether the response includes sensitive data
- whether this should be proxied through a server route
What a good proxy route does
A proxy route is not just a pass-through.
It should enforce rules:
- reject invalid input
- reject unauthenticated users where needed
- check account ownership or plan entitlement
- rate limit expensive calls
- call the provider with a server-side secret
- handle provider failures safely
- return a small response
Use this prompt:
Create a safe server-side proxy route for this third-party API call.
Requirements:
- secret key remains server-only
- validate all input with clear limits
- authenticate the user if the feature is private
- check authorization or plan entitlement if relevant
- rate limit by user and IP
- call the third-party provider from the server
- log only safe metadata
- return only fields the UI needs
- include test cases or manual verification steps
Avoid proxying blindly
Do not proxy everything just because the word sounds safe.
Public, cacheable assets can stay public.
The proxy is most useful when there is:
- a secret
- a cost
- a permission check
- sensitive input
- sensitive output
- abuse potential
- a provider response you need to sanitize
If none of those exist, a direct browser call may be fine.
Watch for generated SDK examples
Many provider docs show simple examples.
AI tools may copy those examples into the wrong environment.
For example:
Create a client with an API key, then call the provider.
That might be safe in a server file.
It is unsafe in a client component.
Ask:
Check whether any SDK client that uses a secret is imported by client components, browser bundles, shared utilities used by client code, or files with a client directive.
Verify from the browser
After moving sensitive calls server-side:
- Open DevTools.
- Trigger the feature.
- Inspect network requests.
- Confirm the browser calls your domain, not the privileged provider directly.
- Confirm no secret key appears in request headers, payloads, responses or bundles.
If the browser still talks directly to the privileged API, the fix is incomplete.
What PageLens can help with
PageLens can catch public launch-readiness symptoms and security posture issues.
It cannot know every third-party credential in your source code. That is why this lesson pairs browser inspection, code search and PageLens scanning.
The rule is simple:
If the call needs trust, put it behind your server.
Related routing safety
Once sensitive calls are behind your server, review the public routing layer too: