Handling Secrets and Proxies with Nuxt 3 Server Routes
Stop leaking API keys in your frontend. Learn how to use Nuxt 3 server routes and Nitro to create secure proxies and manage private runtime configurations.
10 Aug 2025, 22:41 UTC

The Client-Side Secret Leak
A common challenge when building Nuxt applications is interacting with third-party APIs that require a private API key. If you call these APIs directly from your Vue components using useFetch, your secret key is bundled into the JavaScript sent to the browser. Anyone who opens the Network tab in their browser's developer tools can steal that key, potentially leading to exhausted quotas or compromised accounts.
The solution is to move the sensitive request to a server-side intermediary. Nuxt 3 provides this via the server/ directory, powered by the Nitro engine. By creating a server route, you create a secure bridge: the client talks to your Nuxt server, and your Nuxt server talks to the third-party API using a hidden key.
How Nitro Server Routes Work
Nuxt 3 uses a file-system based router for its server engine. Any file placed in server/api/ automatically becomes an API endpoint. For example, a file at server/api/data.ts is accessible at /api/data without any manual route configuration.
These routes use H3, a minimal HTTP framework. Instead of the traditional Express-style (req, res) pattern, Nitro uses an event object. This object contains helper functions like readBody, getQuery, and setCookie, which simplify common request handling tasks.
Managing Secrets with Runtime Config
To keep keys secure, you should use the runtimeConfig property in nuxt.config.ts. Nuxt distinguishes between public keys and private keys:
- Public: Placed inside the
publicobject; these are available to both the server and the client. - Private: Placed at the root of
runtimeConfig; these are only accessible on the server.
Worked Example: A Secure Weather Proxy
In this scenario, we want to fetch weather data from an external provider without exposing our API key to the user. We will create a proxy route that handles the authentication on the server.
1. Configure the secret in nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
// Private key (server-side only)
weatherApiKey: process.env.WEATHER_API_KEY,
public: {
// Public key (client and server)
apiBase: '/api'
}
}
})
2. Create the server route at server/api/weather.ts:
// Run this on the server. Requires Nuxt 3+ and Nitro
export default defineEventHandler(async (event) => {
// Access the private runtime config
const config = useRuntimeConfig();
// Get the city from the query string (e.g., /api/weather?city=London)
const query = getQuery(event);
const city = query.city || 'New York';
try {
const data = await $fetch(`https://api.weatherprovider.com/v1/current?q=${city}&key=${config.weatherApiKey}`);
// Return only the necessary data to the client to reduce payload size
return {
temp: data.current.temp_c,
condition: data.current.condition.text
};
} catch (error) {
throw createError({
statusCode: 502,
statusMessage: 'External Weather API failed to respond',
});
}
});
3. Call the route from a Vue component:
<script setup>
const { data, error } = await useFetch('/api/weather', {
query: { city: 'Tokyo' }
});
</script>
Deployment and Limitations
One of Nitro's strongest features is its deployment flexibility. These server routes are compiled into a format that can run on Node.js, Vercel, Netlify, or Cloudflare Workers without changing the code. However, this abstraction introduces specific constraints:
| Consideration | Server Route Behavior | Dedicated Backend Behavior |
|---|---|---|
| Execution Time | Limited by serverless timeouts (e.g., 10-30s). | Can run long-running background jobs. |
| State | Stateless; requires external DB/Redis. | Can maintain persistent socket connections. |
| Coupling | Tightly coupled to the frontend deployment. | Independent scaling and versioning. |
If your application requires WebSockets or heavy computational processing, a dedicated backend (like Go, Python, or a separate Node.js API) is more appropriate. Use server routes for API orchestration, secret masking, and lightweight data transformation.
Verification and Safety
To verify your implementation, run the project in production mode (npm run build and npm run preview). Open the browser's Network tab and inspect the request to /api/weather. You should see the filtered JSON response, but the weatherApiKey should never appear in the request URL or the response body.
Rollback: If you accidentally committed a secret key to your version control, changing the value in .env is not enough. You must rotate the API key immediately at the provider level, as the old key remains in your git history.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.