Independent developer field guide
Edition 0012026
Guide · beginner

Choose a serverless host for AI API calls

Keep an OpenAI or Claude API key out of the browser. Compares serverless function limits and streaming on Netlify, Vercel and Cloudflare.

Before you start

  • A static or framework site that already builds with npm run build
  • An API key from OpenAI or Anthropic, kept in your password manager and never pasted into code
  • A Git repository connected to the host you pick
  • Basic comfort with environment variables and the browser developer tools

This guide is for a site that calls an AI model, such as OpenAI or Claude, and needs the key to stay private. It explains why the call belongs in a serverless function, what each host documents about functions, and which limits we could confirm on official pages. The steps follow official documentation. We did not run these steps end to end for this guide, so treat the commands as untested until you run them on your own project. Where a limit was not on the official page we read, we say so and link the docs to check.

This is a comparison for choosing a host, not a ranking. Pick the host you already deploy to unless one of the limits below would break your use case.

Why the key must stay on the server

A browser downloads every file it needs to run your site, including the JavaScript. If the provider key is in that JavaScript, anyone can copy it and spend your credits. OpenAI's production best practices say: "Avoid exposing the API keys in your code or in public repositories" (OpenAI production best practices). Anthropic's API key guidance gives the same rule in its own words: "Never include it directly in your code or configuration files" (Claude API key best practices).

Build tools make the same point from another side. Vite warns that variables with its VITE_ prefix are bundled into client code, so they must not hold sensitive values. Astro exposes only PUBLIC_ variables to the browser. Both rules mean a provider key should never carry a public prefix.

The fix is a small function on the host. The browser sends the user's question to a path on your own site. The function reads the key from the host's secret store, calls the provider, and returns the answer. The browser never sees the key.

What a function needs from the host

Four things matter for an AI call. The first is where the key is stored. The second is the maximum time a request may run, because a model answer can take a while. The third is the request and response size. The fourth is whether the answer can stream to the page as it is produced.

Netlify, Vercel and Cloudflare each support this pattern. Their official pages say different things about limits, so compare them against your own answer length and traffic.

Netlify

Netlify's functions overview describes a function as a file in your project that handles an event, and a web request returns a response to the client (Netlify Functions overview). Environment variables are set in the Netlify UI, the CLI or the API. A variable can be limited to the Functions scope, so it is available to functions but not to builds. Netlify also lets you mark a variable as containing secret values. The environment variables overview lists these scopes at Netlify environment variables.

Limits: the pages we read did not state a function timeout or a streaming rule that we could cite. Check the current docs for the timeout your plan allows and whether streaming is supported for your function type before you design around it: Netlify Functions overview.

Vercel

Vercel's function limits page gives concrete numbers. Hobby functions have a maximum duration of 300 seconds. On Pro and Enterprise the default is 300 seconds and the maximum is 800 seconds, and a 1800-second extended maximum is listed as beta. The page also says a function that does not finish within its duration returns a 504 error. The maximum payload for a request or response body is 4.5 MB, and larger payloads return a 413 error. Memory is 2 GB on Hobby and 4 GB on Pro and Enterprise (Vercel function limitations).

For streaming, the Vercel streaming page says a function can return a streamed response, and it recommends the AI SDK to reduce the work of streaming from AI providers. Node.js functions can be configured with longer maximum durations so streamed responses can run longer (Vercel streaming functions).

Environment variables are set per environment, and the page says source code can read them during the build step or during function execution (Vercel environment variables).

Cloudflare

Cloudflare Pages Functions run on Cloudflare Workers, and the Pages limits page says requests to Pages Functions count toward your Workers plan quota (Cloudflare Pages limits). The Workers limits page measures CPU time, not waiting time: it says waiting on network requests such as fetch calls does not count toward CPU time. On the Workers Paid plan the default CPU limit is 30 seconds and can be raised to 5 minutes. The page also gives the request body limit as depending on the domain's plan, with a 413 error when it is exceeded (Cloudflare Workers limits).

For streaming, the Workers Streams page says a Worker does not need to prepare the whole response body before returning it. A ReadableStream can be used as the response body, and data is sent to the client as it becomes available (Cloudflare Workers Streams).

Secrets are stored encrypted. The Workers secrets page says: "Do not use vars to store sensitive information in your Worker's Wrangler configuration file. Use secrets instead" (Cloudflare Workers secrets).

How to decide

Use these questions. If your answers are short, a normal function on any of the three hosts will do. If they are long, check the limits above first.

  • How long can one answer take, including the time the provider spends thinking? Compare with each host's maximum duration.
  • Do you need the answer to appear word by word? Check streaming support on the host you choose.
  • How large is the input you will send? Check the body size limit.
  • Do you already have a deploy pipeline on one host? Stay there unless a limit fails your test.

Step 1: Store the key in the host's secret store

Never paste the key into a file in the repository, and never add it to a public variable. Add it to the host as a secret for functions only.

For Cloudflare Workers or Pages Functions, run this from the project folder, then paste the key when asked:

npx wrangler secret put OPENAI_API_KEY

The Cloudflare page says the command creates a new version of the Worker and deploys it immediately. Use the matching name for your provider, such as ANTHROPIC_API_KEY. We did not run this step for this guide.

For Netlify, add a variable in the Netlify UI with the Functions scope. For Vercel, add it under the environment you use for production. The Vercel page says these values are encrypted at rest. Keep the Netlify secrets option turned on when the UI offers it.

Step 2: Write the function

Create one function that accepts a POST request with the user's question, checks the input length, calls the provider, and returns only the text the page needs. Read the key from the environment inside the function, not at the top of a shared module. Return a generic error to the browser when the provider call fails. Do not echo the provider's raw error text if it could contain the request headers.

Put the function at the path the host expects. For Netlify, the overview shows files under netlify/functions. For Vercel route handlers, the streaming page uses app/api paths. For Cloudflare Pages Functions, use the functions folder described in the Pages docs. Check the file layout in the docs for your framework before you copy a path.

We did not run this step for this guide.

Step 3: Turn on streaming only if you need it

Streaming shows the answer as it arrives, which helps long answers feel quick. It also makes the response last longer, so the duration limit matters more. On Vercel, streaming is documented for functions, and the AI SDK is the recommended helper. On Cloudflare, return a Response built from the provider's response body stream, as the Streams page shows. On Netlify, check the current docs before you design around streaming.

Start without streaming. Add it when the wait is a real problem.

We did not run this step for this guide.

Step 4: Add basic rate limiting

A function that anyone can call can be used to spend your credits. Start with these basics:

  • Cap the length of the user's input and the maximum output your call requests from the provider.
  • Limit how many requests one visitor can send in a short period. Use a counter in your host's storage or a service you already trust. The exact tool depends on your host, so check its docs before you choose.
  • Set a spend limit in the provider console. Anthropic's guidance says: "These limits act as a safeguard against unexpected usage due to leaked keys or errant scripts" (Claude API key best practices).
  • Handle 429 responses in the function. OpenAI's rate limit page says: "Retry-After may be present on 429 responses" (OpenAI rate limits). Read that header when it exists, wait, and retry a small number of times.

We did not run this step for this guide.

Step 5: Log errors without the key

Log the status code, the provider name, the request id, and a short error message. Do not log the key, the full request headers, or the full user prompt, because logs are often shared with more people than the source code. The function logs on each host show these entries in the dashboard or the CLI. Check the host docs for where your logs appear.

Rotate the key if you ever see it in a log. Anthropic's guidance says to revoke a suspected compromised key immediately, and OpenAI's production page recommends setting an expiration date and a regular rotation process for project keys.

We did not run this step for this guide.

Step 6: Build and test the path

Build the site locally, then check that the key is not in the output:

npm run build
grep -r "$OPENAI_API_KEY" dist

The grep searches the build output for the value of your local key variable. It should print nothing. Run it with the variable exported in your shell, and repeat it for each provider key you use. If anything prints, remove it from the source and rotate the key.

After you deploy, call the function from the browser developer tools or with a request from your terminal. Confirm it returns an answer, then send an oversized input and check that you get a clean error rather than a timeout. Check the function logs for the error entry, and confirm the key does not appear there.

We did not run this step for this guide. The commands are standard, not run on a real project.

Known limits and what to check

  • Vercel: 300-second maximum on Hobby, 800 seconds on Pro and Enterprise for the stable plan, 4.5 MB payload limit.
  • Cloudflare: CPU time limit of 30 seconds by default on the Paid plan, with a maximum of 5 minutes. Waiting on the provider does not count toward CPU time.
  • Netlify: the pages we read did not state a timeout or a streaming rule. Check the current docs at Netlify Functions overview.
  • Provider limits change by model and account. Check the current docs and your own console before launch.

Checklist before you launch

  • The provider key exists only in the host's secret store and in no repository file.
  • The build output contains no provider key, and grep confirms it.
  • The function caps input length and output length.
  • A burst of requests from one visitor hits a limit you chose.
  • A 429 or a timeout returns a clear error to the page.
  • Logs show the error without the key or the full prompt.
  • The provider spend limit is set in the provider console.

Expected result: The browser calls your own function path, the function calls the model provider, and the answer comes back to the page. The provider key appears in no file the browser downloads, and the function logs show errors without showing the key.

If something goes wrong

Problem Fix
Provider key is visible in the page source or in the built JavaScript Move the call into a serverless function and read the key from the host environment. Search the build output for the key prefix and rotate the key if it appears.developers.openai.com
Function returns 504 or times out on a long answer Check the maximum duration for your plan on the host, enable streaming where the host supports it, or shorten the answer with a lower max token setting.vercel.com
Function returns 413 on a large request Vercel limits request and response bodies to 4.5 MB. Cloudflare request body limits depend on the domain plan. Trim the input or send less text per call.vercel.com
Provider returns 429 Too Many Requests Wait for the time in the Retry-After header when it is present, back off between retries, and check the rate limits for your model in the provider console.developers.openai.com
Cloudflare Worker stops with an exceeded CPU time error Heavy work counts toward CPU time. Move parsing and large payload work out of the request, or raise the CPU limit within your plan. Waiting on the model provider does not count.developers.cloudflare.com