Skip to content

Connecting a frontend to a backend

You’ve deployed two projects: a frontend and an API. Now the frontend needs to call the API. There are two ways to do it, and one setting that catches almost everyone who picks the second.

Option 1 — call the API from the browser

Section titled “Option 1 — call the API from the browser”

The simplest setup. Your frontend fetches the API’s own URL directly:

const res = await fetch(`${import.meta.env.VITE_API_URL}/api/pages/tree`);

with the API’s public URL in an environment variable:

VITE_API_URL=https://myapi-ab12cd34.pandastack.app

Because the request comes from a browser on a different origin, your API must send CORS headers. In ASP.NET Core:

builder.Services.AddCors(o => o.AddDefaultPolicy(p => p
.WithOrigins("https://app.example.com")
.AllowAnyHeader()
.AllowAnyMethod()));
app.UseCors();

In Express:

app.use(cors({ origin: "https://app.example.com" }));

Name your real frontend origin rather than using a wildcard — it’s the difference between “anyone can call this API from any page” and “my app can”.

Option 2 — proxy through your frontend server

Section titled “Option 2 — proxy through your frontend server”

If your frontend runs a server of its own — Angular SSR, Next.js, Nuxt, or plain Express — you can forward /api/* to the backend instead. Same-origin requests, so no CORS at all.

This is where the one setting comes in.

import { createProxyMiddleware } from "http-proxy-middleware";
app.use("/api", createProxyMiddleware({
target: process.env.API_BASE_URL,
changeOrigin: true, // ← required
}));

changeOrigin defaults to false. Left alone, your server forwards the browser’s original Host header — so a request meant for your API arrives carrying your frontend’s hostname. PandaStack routes by hostname, so it sends the request back to the frontend, which proxies it again, and the loop is stopped with an error.

Setting changeOrigin: true rewrites the Host header to the target. That one line is the whole fix.

The equivalent option in other tools:

ToolSetting
http-proxy-middleware (Express, Angular SSR)changeOrigin: true
Angular proxy.conf.json"changeOrigin": true
Vite server.proxychangeOrigin: true
Next.js rewrites()handled for you — no setting needed
nginxproxy_set_header Host $proxy_host;
Caddyreverse_proxy rewrites Host by default

Apps in the same organization can reach each other without going out to the internet at all. See Internal networking for the hostnames.

Error 1000: DNS points to prohibited IP / dns_loop

Section titled “Error 1000: DNS points to prohibited IP / dns_loop”

You’ll see this as a 403 from a Cloudflare error page, naming your backend’s hostname, when the request came through your frontend.

Despite what the message says, there is nothing wrong with your DNS, and there are no A records for you to change — the hostname belongs to PandaStack.

It means your proxy forwarded the wrong Host header. Set changeOrigin: true (see Option 2 above).

Quick way to confirm it — the same request, two different Host headers:

Terminal window
# with the backend's own hostname → works
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Host: myapi-ab12cd34.pandastack.app" \
https://myapi-ab12cd34.pandastack.app/api/health
# with the frontend's hostname → 403
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Host: app.example.com" \
https://myapi-ab12cd34.pandastack.app/api/health

If the first returns 200 and the second 403, your backend is healthy and the Host header is the problem.

You’re on Option 1 and the API isn’t sending CORS headers, or it’s not allowing your frontend’s exact origin. Check that the origin in your CORS policy matches your frontend URL exactly — scheme included, no trailing slash.

The first request after an idle period is slow

Section titled “The first request after an idle period is slow”

That’s a cold start. Apps on scale-to-zero shut down when idle and start again on the next request. See Autoscaling & scale-to-zero.