Skip to content
Merged
10 changes: 10 additions & 0 deletions .changeset/sdk-readme-adr036-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@zitadel/sdk-react": patch
"@zitadel/sdk-vue": patch
"@zitadel/sdk-angular": patch
"@zitadel/sdk-solid": patch
"@zitadel/sdk-svelte": patch
"@zitadel/sdk-qwik": patch
---

docs: replace `@zitadel/edge-proxy` production guidance in the package READMEs with the ADR 036 model — platform rewrites/minimal worker plus a publishable key, no secrets on the platform (scaffolding tracked in zitadel/nextgen#560)
3 changes: 2 additions & 1 deletion apps/cli/src/lib/orca/patchers/rule/angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ Integrates Zitadel auth into an Angular app.
The SDK widgets call `/__nextgen/*` same-origin. In dev, `proxy.conf.cjs`
forwards those to the backend and attaches the project service-key secret (read
from `ZITADEL_PROJECT_SECRET` in `.env.local`) only to `POST /sessions/exchange`.
Production needs `@zitadel/edge-proxy` in front.
The production story is a platform rewrite plus the publishable key from ADR 036
(`docs/adrs/036-api-credential-planes.md`), tracked in issue #560.
4 changes: 3 additions & 1 deletion apps/cli/src/lib/orca/patchers/rule/angular/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ function ensureDevScript(source: string | undefined): string {
*
* Unlike React/Vue (whose dev proxy lives in `vite.config.ts`), Angular owns its
* Vite config, so the proxy is a separate `proxy.conf.cjs` referenced from the
* `serve` target. Production still needs `@zitadel/edge-proxy`.
* `serve` target. In production the same-origin path comes from a platform
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
* issue #560.
*/
export class AngularPatcher extends AbstractRulePatcher {
canPatch(framework: string): boolean {
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/qwik/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ Integrates Zitadel auth into a Vite + Qwik single-page app.

The SDK widgets call `/__nextgen/*` same-origin. In dev, the Vite proxy forwards
those to the backend and attaches the project service-key secret (read from
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. Production needs
`@zitadel/edge-proxy` in front.
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. The production
story is a platform rewrite plus the publishable key from ADR 036
(`docs/adrs/036-api-credential-planes.md`), tracked in issue #560.
5 changes: 3 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/qwik/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ const SDK_DEPENDENCY = "@zitadel/sdk-qwik";
* The create-vite Qwik template uses a lowercase `src/app.tsx` exporting a named
* `App` (mounted by `main.tsx`), so this patcher writes that exact entry. Unlike
* Next.js — whose middleware runs the proxy server-side — a SPA has no server,
* so the dev proxy stands in for `@zitadel/edge-proxy` locally. Production
* deployments still need that proxy.
* so the dev proxy provides the same-origin `/__nextgen` path locally. In
* production that path comes from a platform rewrite or minimal worker
* (ADR 036); CLI scaffolding for it is tracked in issue #560.
*/
export class QwikPatcher extends AbstractRulePatcher implements ViteSupport {
canPatch(framework: string): boolean {
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ Integrates Zitadel auth into a Vite + React single-page app.

The SDK widgets call `/__nextgen/*` same-origin. In dev, the Vite proxy forwards
those to the backend and attaches the project service-key secret (read from
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. Production needs
`@zitadel/edge-proxy` in front.
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. The production
story is a platform rewrite plus the publishable key from ADR 036
(`docs/adrs/036-api-credential-planes.md`), tracked in issue #560.
6 changes: 4 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ const SDK_DEPENDENCY = "@zitadel/sdk-react";
* project id, and the SDK dep.
*
* Unlike Next.js — whose middleware runs the proxy and token exchange
* server-side — a SPA has no server, so the dev proxy stands in for
* `@zitadel/edge-proxy` locally. Production deployments still need that proxy.
* server-side — a SPA has no server, so the dev proxy provides the same-origin
* `/__nextgen` path locally. In production that path comes from a platform
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
* issue #560.
*/
export class ReactPatcher extends AbstractRulePatcher implements ViteSupport {
canPatch(framework: string): boolean {
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/solid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ Integrates Zitadel auth into a Vite + Solid single-page app.

The SDK widgets call `/__nextgen/*` same-origin. In dev, the Vite proxy forwards
those to the backend and attaches the project service-key secret (read from
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. Production needs
`@zitadel/edge-proxy` in front.
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. The production
story is a platform rewrite plus the publishable key from ADR 036
(`docs/adrs/036-api-credential-planes.md`), tracked in issue #560.
6 changes: 4 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/solid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ const SDK_DEPENDENCY = "@zitadel/sdk-solid";
* project id, and the SDK dep.
*
* Unlike Next.js — whose middleware runs the proxy and token exchange
* server-side — a SPA has no server, so the dev proxy stands in for
* `@zitadel/edge-proxy` locally. Production deployments still need that proxy.
* server-side — a SPA has no server, so the dev proxy provides the same-origin
* `/__nextgen` path locally. In production that path comes from a platform
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
* issue #560.
*/
export class SolidPatcher extends AbstractRulePatcher implements ViteSupport {
canPatch(framework: string): boolean {
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/svelte/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ Integrates Zitadel auth into a Vite + Svelte single-page app.

The SDK widgets call `/__nextgen/*` same-origin. In dev, the Vite proxy forwards
those to the backend and attaches the project service-key secret (read from
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. Production needs
`@zitadel/edge-proxy` in front.
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. The production
story is a platform rewrite plus the publishable key from ADR 036
(`docs/adrs/036-api-credential-planes.md`), tracked in issue #560.
6 changes: 4 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/svelte/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ const SDK_DEPENDENCY = "@zitadel/sdk-svelte";
* project id, and the SDK dep.
*
* Unlike Next.js — whose middleware runs the proxy and token exchange
* server-side — a SPA has no server, so the dev proxy stands in for
* `@zitadel/edge-proxy` locally. Production deployments still need that proxy.
* server-side — a SPA has no server, so the dev proxy provides the same-origin
* `/__nextgen` path locally. In production that path comes from a platform
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
* issue #560.
*/
export class SveltePatcher extends AbstractRulePatcher implements ViteSupport {
canPatch(framework: string): boolean {
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/lib/orca/patchers/rule/vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ Integrates Zitadel auth into a Vite + Vue single-page app.

The SDK widgets call `/__nextgen/*` same-origin. In dev, the Vite proxy forwards
those to the backend and attaches the project service-key secret (read from
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. Production needs
`@zitadel/edge-proxy` in front.
`ZITADEL_PROJECT_SECRET`) only to `POST /sessions/exchange`. The production
story is a platform rewrite plus the publishable key from ADR 036
(`docs/adrs/036-api-credential-planes.md`), tracked in issue #560.
6 changes: 6 additions & 0 deletions apps/docs/content/docs/concepts/sdk-proxy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ The default proxy path is:
```

The login and logout components call the same-origin proxy path by default when configured through the SDK.

## SPAs in production

Single-page apps (React, Vue, Angular, Solid, Svelte, Qwik) have no server of their own. In local development the CLI scaffolds a dev-server proxy for `/__nextgen/*`; in production the same-origin path comes from the hosting platform instead: a `vercel.json` rewrite, a `netlify.toml` redirect, or a minimal Cloudflare worker (Cloudflare redirects cannot proxy external origins).

[ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md) removes the project-secret requirement from this browser-facing path — a browser-safe, origin-checked credential replaces it, with the project's origin allowlist as the enforcement boundary — so these platform entries are plain config with no secrets in platform env stores. The credential rollout and CLI scaffolding for the platform configs are tracked in [zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560); until that lands, the dev proxy covers local development only.
2 changes: 1 addition & 1 deletion apps/docs/content/docs/sdks/angular.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ The Angular SDK exposes standalone components that wrap the shared Zitadel auth

<Steps>
<Step>The Angular wrapper renders the underlying custom element.</Step>
<Step>The generated dev proxy forwards `/__nextgen/*` and attaches the project service key.</Step>
<Step>The generated dev proxy forwards `/__nextgen/*` and attaches the project service key only on the session exchange.</Step>
<Step>Production deployments need an equivalent proxy.</Step>
</Steps>

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/sdks/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ All packages ship on the `@alpha` dist-tag and move together as one train.
| Components | `@zitadel/components` | Web components | n/a (wrapped by an SDK) | — |
| API client | `@zitadel/api` | n/a | n/a | typed client |

The same-origin proxy is the one concept shared by every SDK. See [SDK proxy](/docs/concepts/sdk-proxy) for why `/__nextgen/*` must be same-origin and how the project service key is attached.
The same-origin proxy is the one concept shared by every SDK. See [SDK proxy](/docs/concepts/sdk-proxy) for why `/__nextgen/*` must be same-origin and how credentials are scoped to it.
4 changes: 2 additions & 2 deletions apps/docs/content/docs/sdks/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ The React SDK is the SPA wrapper for the shared Zitadel login and logout widgets

<Steps>
<Step>The widgets call the configured proxy path on the same origin.</Step>
<Step>In local Vite development, the generated proxy forwards `/__nextgen/*` to Zitadel and attaches the project service key.</Step>
<Step>In production, deploy an equivalent edge/backend proxy or intentionally configure a backend URL for local-only experiments.</Step>
<Step>In local Vite development, the generated proxy forwards `/__nextgen/*` to Zitadel and attaches the project service key only on the session exchange.</Step>
<Step>In production, the same-origin path comes from a platform rewrite or minimal worker (see [SDK proxy](/docs/concepts/sdk-proxy)) — or intentionally configure a backend URL for local-only experiments.</Step>
</Steps>

## Verify
Expand Down
12 changes: 8 additions & 4 deletions packages/sdk-angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export class LoginPage {

## Proxying to the backend (deployment)

The widgets call `${proxyPath}/…` same-origin (default `/__nextgen`). On Vercel use
`@zitadel/edge-proxy` to forward those calls (see the React/Vue READMEs for the
`vercel.json` rewrite + edge function). Locally you can point `proxyPath` straight at
the backend (cross-origin), e.g. `proxyPath: "http://localhost:4000"`.
The widgets call `${proxyPath}/…` same-origin (default `/__nextgen`). In production
that path comes from your hosting platform — a `vercel.json` rewrite, a
`netlify.toml` redirect, or a minimal Cloudflare worker — per
[ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md)
(scaffolding tracked in [zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560);
**until that work lands, production SPA deployment is not yet supported**).
Locally you can point `proxyPath` straight at the backend (cross-origin), e.g.
`proxyPath: "http://localhost:4000"`.
28 changes: 8 additions & 20 deletions packages/sdk-qwik/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,14 @@ The widget's flow events are surfaced as optional QRL callbacks:
## Proxying to the backend (deployment)

The widgets call `${proxyPath}/…` same-origin (default `/__nextgen`). A SPA has no
server, so on Vercel use `@zitadel/edge-proxy` to forward those calls:

```ts
// api/__nextgen/[...path].ts (Vercel Edge Function)
import { handleProxy, resolveConfig } from '@zitadel/edge-proxy';
export const config = { runtime: 'edge' };
const proxyConfig = resolveConfig({
apiUrl: process.env.NEXTGEN_API_URL ?? '',
});
export default (req: Request) => handleProxy(req, proxyConfig);
```

```json
// vercel.json
{
"rewrites": [
{ "source": "/__nextgen/(.*)", "destination": "/api/__nextgen/$1" }
]
}
```
server, so in production the path comes from your hosting platform — a
`vercel.json` rewrite, a `netlify.toml` redirect, or a minimal Cloudflare
worker — with no secrets on the platform, per
[ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md).
CLI scaffolding for these configs is tracked in
[zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560). **Until
that work lands, production SPA deployment is not yet supported** — the CLI dev
proxy covers local development.

For local development you can skip the proxy and point `proxyPath` straight at the
backend (cross-origin), e.g. `proxyPath: "http://localhost:4000"`.
30 changes: 8 additions & 22 deletions packages/sdk-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,14 @@ export function ProfilePage() {
## Proxying to the backend (deployment)

The widgets call `${proxyPath}/…` same-origin (default `/__nextgen`). A SPA has no
server, so on Vercel (and Cloudflare/Netlify) use `@zitadel/edge-proxy` to forward
those calls to your Zitadel backend:

```ts
// api/__nextgen/[...path].ts (Vercel Edge Function)
import { handleProxy, resolveConfig } from '@zitadel/edge-proxy';

export const config = { runtime: 'edge' };
const proxyConfig = resolveConfig({
apiUrl: process.env.NEXTGEN_API_URL ?? '',
});
export default (req: Request) => handleProxy(req, proxyConfig);
```

```json
// vercel.json
{
"rewrites": [
{ "source": "/__nextgen/(.*)", "destination": "/api/__nextgen/$1" }
]
}
```
server, so in production the path comes from your hosting platform — a
`vercel.json` rewrite, a `netlify.toml` redirect, or a minimal Cloudflare
worker — with no secrets on the platform, per
[ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md).
CLI scaffolding for these configs is tracked in
[zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560). **Until
that work lands, production SPA deployment is not yet supported** — the CLI dev
proxy covers local development.

For local development you can skip the proxy entirely and point `proxyPath` straight
at the backend (cross-origin), e.g. `proxyPath: "http://localhost:4000"`.
28 changes: 8 additions & 20 deletions packages/sdk-solid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,14 @@ The widget's flow events are also surfaced as optional callbacks:
## Proxying to the backend (deployment)

The widgets call `${proxyPath}/…` same-origin (default `/__nextgen`). A SPA has no
server, so on Vercel use `@zitadel/edge-proxy` to forward those calls:

```ts
// api/__nextgen/[...path].ts (Vercel Edge Function)
import { handleProxy, resolveConfig } from '@zitadel/edge-proxy';
export const config = { runtime: 'edge' };
const proxyConfig = resolveConfig({
apiUrl: process.env.NEXTGEN_API_URL ?? '',
});
export default (req: Request) => handleProxy(req, proxyConfig);
```

```json
// vercel.json
{
"rewrites": [
{ "source": "/__nextgen/(.*)", "destination": "/api/__nextgen/$1" }
]
}
```
server, so in production the path comes from your hosting platform — a
`vercel.json` rewrite, a `netlify.toml` redirect, or a minimal Cloudflare
worker — with no secrets on the platform, per
[ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md).
CLI scaffolding for these configs is tracked in
[zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560). **Until
that work lands, production SPA deployment is not yet supported** — the CLI dev
proxy covers local development.

For local development you can skip the proxy and point `proxyPath` straight at the
backend (cross-origin), e.g. `proxyPath: "http://localhost:4000"`.
28 changes: 8 additions & 20 deletions packages/sdk-svelte/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,14 @@ The widget's flow events are also surfaced as optional callbacks:
## Proxying to the backend (deployment)

The widgets call `${proxyPath}/…` same-origin (default `/__nextgen`). A SPA has no
server, so on Vercel use `@zitadel/edge-proxy` to forward those calls:

```ts
// api/__nextgen/[...path].ts (Vercel Edge Function)
import { handleProxy, resolveConfig } from '@zitadel/edge-proxy';
export const config = { runtime: 'edge' };
const proxyConfig = resolveConfig({
apiUrl: process.env.NEXTGEN_API_URL ?? '',
});
export default (req: Request) => handleProxy(req, proxyConfig);
```

```json
// vercel.json
{
"rewrites": [
{ "source": "/__nextgen/(.*)", "destination": "/api/__nextgen/$1" }
]
}
```
server, so in production the path comes from your hosting platform — a
`vercel.json` rewrite, a `netlify.toml` redirect, or a minimal Cloudflare
worker — with no secrets on the platform, per
[ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md).
CLI scaffolding for these configs is tracked in
[zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560). **Until
that work lands, production SPA deployment is not yet supported** — the CLI dev
proxy covers local development.

For local development you can skip the proxy and point `proxyPath` straight at the
backend (cross-origin), e.g. `proxyPath: "http://localhost:4000"`.
28 changes: 8 additions & 20 deletions packages/sdk-vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,14 @@ const project = configureZitadel({
## Proxying to the backend (deployment)

The widgets call `${proxyPath}/…` same-origin (default `/__nextgen`). A SPA has no
server, so on Vercel use `@zitadel/edge-proxy` to forward those calls:

```ts
// api/__nextgen/[...path].ts (Vercel Edge Function)
import { handleProxy, resolveConfig } from '@zitadel/edge-proxy';
export const config = { runtime: 'edge' };
const proxyConfig = resolveConfig({
apiUrl: process.env.NEXTGEN_API_URL ?? '',
});
export default (req: Request) => handleProxy(req, proxyConfig);
```

```json
// vercel.json
{
"rewrites": [
{ "source": "/__nextgen/(.*)", "destination": "/api/__nextgen/$1" }
]
}
```
server, so in production the path comes from your hosting platform — a
`vercel.json` rewrite, a `netlify.toml` redirect, or a minimal Cloudflare
worker — with no secrets on the platform, per
[ADR 036](https://github.com/zitadel/nextgen/blob/main/docs/adrs/036-api-credential-planes.md).
CLI scaffolding for these configs is tracked in
[zitadel/nextgen#560](https://github.com/zitadel/nextgen/issues/560). **Until
that work lands, production SPA deployment is not yet supported** — the CLI dev
proxy covers local development.

For local development you can skip the proxy and point `proxyPath` straight at the
backend (cross-origin), e.g. `proxyPath: "http://localhost:4000"`.
Loading