Most React tutorials instruct developers to store API keys in an .env file. While this prevents keys from being committed to GitHub, it is important to understand that environment variables prefixed with VITE_ or REACT_APP_ are embedded into the client-side bundle.
Any key used in a client-side fetch() or axios call is still visible to anyone who opens the browser's "Network" tab. For a public API like TMDB, this exposes your quota and potentially your account to malicious actors.
Instead of calling the TMDB API directly from the React components, I architected a Backend Proxy using Netlify Functions. This ensures the API key never leaves the server environment.
- Frontend: Sends a request to a local endpoint:
/.netlify/functions/get-movies?query=... - Serverless Function: Resides on the server, retrieves the
TMDB_API_KEYfrom the secure environment variables, and makes the actual request to TMDB. - Frontend: Receives the clean JSON data without ever seeing the secret key.
// DON'T DO THIS: The key is leaked to the browser's Network tab!
const API_KEY = import.meta.env.VITE_TMDB_KEY;
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`,
);Create a file at netlify/functions/get-movies.js:
exports.handler = async (event) => {
const API_KEY = process.env.TMDB_API_KEY; // Safely accessed on the server
const { query } = event.queryStringParameters;
try {
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`,
);
const data = await response.json();
return {
statusCode: 200,
body: JSON.stringify(data),
};
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};To make the search experience seamless while the proxy is fetching data, I implemented:
useTransition: Manages the UI state during the "search" transition, ensuring the input remains responsive even during network latency.useActionState: Handles the search form submission and state management natively, reducing boilerplate.- Skeleton Screens: Improved perceived performance by replacing traditional loading spinners with layout placeholders that mimic the content structure.
As a trainer, I emphasize these three pillars for production-grade frontend engineering:
- Never trust the client-side: Sensitive logic and secrets must stay on the server.
- Proxy third-party APIs: Always use serverless functions or a backend to protect your quotas and keys.
- Manage Network Resources: Use
AbortControllerto cancel pending requests when a user navigates away or types a new query rapidly. This prevents race conditions and saves bandwidth.