Calling AI From the Server

Keep AI API keys off the client by calling the provider from a server route your React app fetches.

TL;DR

  1. Never expose an AI apiKey in browser JavaScript.
  2. Call the provider from a server routeHandler, not the client.
  3. The client fetches your /api/chat, which calls the model.

The Client Fetches Your Route

    Post the Prompt

    The React client only ever talks to your own route.

    await fetch('/api/chat', {
      method: 'POST',
      body: JSON.stringify({ prompt }),
    });
    No Secrets in Client

    The browser never sees or sends the API key.

    // zero apiKey anywhere in the bundle
    Read the Answer

    Await JSON, or read a stream for live tokens.

    const { text } = await res.json();

Keep the Key Server-Side

    Server-Only Env

    Store the key where the client cannot read it.

    # .env.local
    AI_API_KEY=sk-...
    No Public Prefix

    Never use NEXT_PUBLIC_ for a secret value.

    // NEXT_PUBLIC_* ships to the browser
    Read at Runtime

    The handler reads the key on the server.

    const key = process.env.AI_API_KEY;

Build the Route Handler

    Define the Route

    A POST handler receives the user's prompt.

    export async function POST(req: Request) {
      const { prompt } = await req.json();
    }
    Call the Provider

    Use the server SDK with your secret key.

    const out = await callModel(prompt, key);
    Return the Result

    Send JSON or a streaming Response back.

    return Response.json({ text: out });

Add Guards

    Validate Input

    Reject empty or oversized prompts early.

    if (!prompt || prompt.length > 4000)
      return new Response('Bad', { status: 400 });
    Require Auth

    Check a session before spending tokens.

    if (!session)
      return new Response('Unauth', { status: 401 });
    Rate Limit

    Cap requests per user to control cost.

    if (await overLimit(userId))
      return new Response('Slow', { status: 429 });

Tips

  1. Read the key from a server process.env variable that is never prefixed for the client, so it never ships in the bundle.
  2. Run the route on the edge runtime for low latency and native streaming when your provider and SDK support it.

Warnings

  1. A key in a NEXT_PUBLIC_ variable or client fetch is public; anyone can read it from the network tab and rack up charges.
  2. Do not skip input validation and auth on the route; an open /api/chat is a free proxy for anyone to abuse.

In Practice

FAQ