Repository: ory/elements
Pull request #484: fix: correctly guess vercel environment
Author: login: aeneasr
Labels: name: Needs Triage
Created: 2025-04-22T12:14:13Z | Merged: 2025-04-23T11:47:56Z

Vercel publishes env vars sometimes prefixed, sometimes not with NEXT_PUBLIC_.

This patch tries to use both variants. Also, if a `vercel.app` domain is found, the middleware will do it's thing. Otherwise, it will use the custom domain.

Closes #482


Linked issue #482: Investigate `VERCEL_ENV=preview` cases
### Preflight checklist

- [x] I could not find a solution in the existing issues, docs, nor discussions.
- [x] I agree to follow this project's [Code of Conduct](https://github.com/ory/elements/blob/master/CODE_OF_CONDUCT.md).
- [x] I have read and am following this repository's [Contribution Guidelines](https://github.com/ory/elements/blob/master/CONTRIBUTING.md).
- [ ] I have joined the [Ory Community Slack](https://slack.ory.sh).
- [ ] I am signed up to the [Ory Security Patch Newsletter](https://www.ory.sh/l/sign-up-newsletter).

### Ory Network Project

_No response_

### Describe your problem

When deploying an application in vercel's preview mode, the middleware might not be behaving as we want.

Preview envs _should_ behave similarly to a production environment, however it seems that it's still trying to guess the URLs.

### Describe your ideal solution

-

### Workarounds or alternatives

-

### Version

master

### Additional Context

_No response_
Linked issue comments: user: login: aeneasr
created_at: 2025-04-22T12:07:03Z
body: If the deployment is not on a custom domain but instead on the vercel.app domain, the middleware should run. You can test this here: https://elements-nextjs-app-router.vercel.app

If the deployment is on a custom domain (so anything but vercel.app), then the proper domain should be used.

I agree that it's probably a side effect of a bug that https://elements-nextjs-app-router.vercel.app works
author_association: MEMBER

Diff:
diff --git a/examples/nextjs-app-router/.env b/examples/nextjs-app-router/.env
index 6ebfdb1ec..6bdc430da 100644
--- a/examples/nextjs-app-router/.env
+++ b/examples/nextjs-app-router/.env
@@ -1 +1,5 @@
-NEXT_PUBLIC_ORY_SDK_URL=https://nervous-lewin-4edwdlsbyw.projects.oryapis.com
\ No newline at end of file
+# This is an example project, please replace it with yours!
+NEXT_PUBLIC_ORY_SDK_URL=https://nervous-lewin-4edwdlsbyw.projects.oryapis.com
+
+# Set the API Token for support of SAML and OIDC.
+ORY_PROJECT_API_TOKEN=
diff --git a/examples/nextjs-app-router/README.md b/examples/nextjs-app-router/README.md
index a7dc40dcc..d183b7cf4 100644
--- a/examples/nextjs-app-router/README.md
+++ b/examples/nextjs-app-router/README.md
@@ -9,17 +9,30 @@ router.
    endpoints** URL
 4. Set `NEXT_PUBLIC_ORY_SDK_URL` in the `.env` file to your project's **API
    endpoints** URL
-5. Run `npm install`
-6. Run `npm run dev` and open navigate to http://localhost:3000
+5. Set `ORY_PROJECT_API_TOKEN` in the `.env` file to your project's **API
+   token**. You can create an
+   [API key in the Ory Console](https://www.ory.sh/docs/concepts/personal-access-token).
+6. Run `npm install`
+7. Run `npm run dev` and open navigate to http://localhost:3000
 
 <!-- prettier-ignore-start -->
 > [!WARNING]
-> For convenience Ory provides a default "playground" project, that
+> For convenience Ory provides a default "test" project, that
 > can be used to interact with Ory's APIs. It is a public project, that can be
 > used by anyone and data can be deleted at any time. Make sure to use a
 > dedicated project.
 <!-- prettier-ignore-end -->
 
+### Deploying to Vercel
+
+To successfully deploy to Vercel, you need to set the following environment
+variables in your Vercel project:
+
+- `NEXT_PUBLIC_ORY_SDK_URL` - The URL of your Ory project. This is the same URL
+  you set in your `.env` file.
+- `ORY_PROJECT_API_TOKEN` - The API token for your Ory project. This should be a
+  dedicated key for production.
+
 ## Features
 
 - All self-service user flows Ory supports
diff --git a/examples/nextjs-pages-router/.env b/examples/nextjs-pages-router/.env
index 6ebfdb1ec..6bdc430da 100644
--- a/examples/nextjs-pages-router/.env
+++ b/examples/nextjs-pages-router/.env
@@ -1 +1,5 @@
-NEXT_PUBLIC_ORY_SDK_URL=https://nervous-lewin-4edwdlsbyw.projects.oryapis.com
\ No newline at end of file
+# This is an example project, please replace it with yours!
+NEXT_PUBLIC_ORY_SDK_URL=https://nervous-lewin-4edwdlsbyw.projects.oryapis.com
+
+# Set the API Token for support of SAML and OIDC.
+ORY_PROJECT_API_TOKEN=
diff --git a/examples/nextjs-pages-router/README.md b/examples/nextjs-pages-router/README.md
index d74b31edf..4d75df208 100644
--- a/examples/nextjs-pages-router/README.md
+++ b/examples/nextjs-pages-router/README.md
@@ -9,8 +9,21 @@ router.
    endpoints** URL
 4. Set `NEXT_PUBLIC_ORY_SDK_URL` in the `.env` file to your project's **API
    endpoints** URL
-5. Run `npm install`
-6. Run `npm run dev` and open navigate to http://localhost:3000
+5. Set `ORY_PROJECT_API_TOKEN` in the `.env` file to your project's **API
+   token**. You can create an
+   [API key in the Ory Console](https://www.ory.sh/docs/concepts/personal-access-token).
+6. Run `npm install`
+7. Run `npm run dev` and open navigate to http://localhost:3000
+
+### Deploying to Vercel
+
+To successfully deploy to Vercel, you need to set the following environment
+variables in your Vercel project:
+
+- `NEXT_PUBLIC_ORY_SDK_URL` - The URL of your Ory project. This is the same URL
+  you set in your `.env` file.
+- `ORY_PROJECT_API_TOKEN` - The API token for your Ory project. This should be a
+  dedicated key for production.
 
 ## Features
 
@@ -24,7 +37,7 @@ router.
 
 <!-- prettier-ignore-start -->
 > [!WARNING]
-> For convenience Ory provides a default "playground" project, that
+> For convenience Ory provides a default "test" project, that
 > can be used to interact with Ory's APIs. It is a public project, that can be
 > used by anyone and data can be deleted at any time. Make sure to use a
 > dedicated project.
diff --git a/examples/nextjs-pages-router/pages/auth/login.tsx b/examples/nextjs-pages-router/pages/auth/login.tsx
index 7e3f47b60..af1cbe729 100644
--- a/examples/nextjs-pages-router/pages/auth/login.tsx
+++ b/examples/nextjs-pages-router/pages/auth/login.tsx
@@ -1,7 +1,7 @@
 // Copyright © 2024 Ory Corp
 // SPDX-License-Identifier: Apache-2.0
 "use client"
-import { Login } from "@ory/elements-react/theme"
+import { DefaultCardLayout, Login } from "@ory/elements-react/theme"
 import { useLoginFlow } from "@ory/nextjs/pages"
 import { enhanceOryConfig } from "@ory/nextjs"
 import "@ory/elements-react/theme/styles.css"
@@ -16,7 +16,7 @@ export default function LoginPage() {
   }
 
   return (
-    <div className="pt-4">
+    <DefaultCardLayout>
       <Login
         flow={flow}
         config={enhanceOryConfig(config)}
@@ -24,6 +24,6 @@ export default function LoginPage() {
           Card: {},
         }}
       />
-    </div>
+    </DefaultCardLayout>
   )
 }
diff --git a/examples/nextjs-pages-router/pages/auth/recovery.tsx b/examples/nextjs-pages-router/pages/auth/recovery.tsx
index 5fb04f9dc..b784dc6dd 100644
--- a/examples/nextjs-pages-router/pages/auth/recovery.tsx
+++ b/examples/nextjs-pages-router/pages/auth/recovery.tsx
@@ -1,7 +1,7 @@
 // Copyright © 2024 Ory Corp
 // SPDX-License-Identifier: Apache-2.0
 "use client"
-import { Recovery } from "@ory/elements-react/theme"
+import { DefaultCardLayout, Recovery } from "@ory/elements-react/theme"
 import { useRecoveryFlow } from "@ory/nextjs/pages"
 import { enhanceOryConfig } from "@ory/nextjs"
 import "@ory/elements-react/theme/styles.css"
@@ -16,7 +16,7 @@ export default function RecoveryPage() {
   }
 
   return (
-    <div className="pt-4">
+    <DefaultCardLayout>
       <Recovery
         flow={flow}
         config={enhanceOryConfig(config)}
@@ -24,6 +24,6 @@ export default function RecoveryPage() {
           Card: {},
         }}
       />
-    </div>
+    </DefaultCardLayout>
   )
 }
diff --git a/examples/nextjs-pages-router/pages/auth/registration.tsx b/examples/nextjs-pages-router/pages/auth/registration.tsx
index 6d85c8a6b..87ea40b43 100644
--- a/examples/nextjs-pages-router/pages/auth/registration.tsx
+++ b/examples/nextjs-pages-router/pages/auth/registration.tsx
@@ -1,7 +1,7 @@
 // Copyright © 2024 Ory Corp
 // SPDX-License-Identifier: Apache-2.0
 "use client"
-import { Registration } from "@ory/elements-react/theme"
+import { DefaultCardLayout, Registration } from "@ory/elements-react/theme"
 import { useRegistrationFlow } from "@ory/nextjs/pages"
 import { enhanceOryConfig } from "@ory/nextjs"
 import "@ory/elements-react/theme/styles.css"
@@ -16,7 +16,7 @@ export default function RegistrationPage() {
   }
 
   return (
-    <div className="pt-4">
+    <DefaultCardLayout>
       <Registration
         flow={flow}
         config={enhanceOryConfig(config)}
@@ -24,6 +24,6 @@ export default function RegistrationPage() {
           Card: {},
         }}
       />
-    </div>
+    </DefaultCardLayout>
   )
 }
diff --git a/examples/nextjs-pages-router/pages/auth/verification.tsx b/examples/nextjs-pages-router/pages/auth/verification.tsx
index 24a142762..6bc36115f 100644
--- a/examples/nextjs-pages-router/pages/auth/verification.tsx
+++ b/examples/nextjs-pages-router/pages/auth/verification.tsx
@@ -1,7 +1,7 @@
 // Copyright © 2024 Ory Corp
 // SPDX-License-Identifier: Apache-2.0
 "use client"
-import { Verification } from "@ory/elements-react/theme"
+import { DefaultCardLayout, Verification } from "@ory/elements-react/theme"
 import { useVerificationFlow } from "@ory/nextjs/pages"
 import { enhanceOryConfig } from "@ory/nextjs"
 import "@ory/elements-react/theme/styles.css"
@@ -16,7 +16,7 @@ export default function VerificationPage() {
   }
 
   return (
-    <div className="pt-4">
+    <DefaultCardLayout>
       <Verification
         flow={flow}
         config={enhanceOryConfig(config)}
@@ -24,6 +24,6 @@ export default function VerificationPage() {
           Card: {},
         }}
       />
-    </div>
+    </DefaultCardLayout>
   )
 }
diff --git a/examples/nextjs-pages-router/pages/index.tsx b/examples/nextjs-pages-router/pages/index.tsx
index 14d63eb40..b0663b824 100644
--- a/examples/nextjs-pages-router/pages/index.tsx
+++ b/examples/nextjs-pages-router/pages/index.tsx
@@ -19,7 +19,7 @@ function HomeContent() {
     <div className="flex items-center justify-center min-h-screen bg-gray-50">
       <div className="flex flex-col items-center gap-4">
         <Image src={OryLogo as string} alt="Ory Logo" width={160} />
-        <h1 className="font-bold text-xl">Ory Next.js App Router Example</h1>
+        <h1 className="font-bold text-xl">Ory Next.js Pages Router Example</h1>
         {!session && (
           <div className="flex items-center gap-2 bg-white rounded border flex-col w-60 p-3">
             <Link className="underline block w-full" href="/auth/registration">
diff --git a/packages/nextjs/README.md b/packages/nextjs/README.md
index 6fb1c876e..97d4cbf85 100644
--- a/packages/nextjs/README.md
+++ b/packages/nextjs/README.md
@@ -1,19 +1,37 @@
 # `@ory/nextjs`
 
-This package contains the Next.js SDK for Ory. It provides a set of React
-components, server-side components, and hooks to interact with the Ory
-ecosystem.
+This package contains the Next.js SDK for Ory. It is additive to the
+`@ory/elements-react` package and provides utility functions for the app and
+page router.
 
-Supports both app and page routers.
+This middleware expects environment variable `NEXT_PUBLIC_ORY_SDK_URL` to be set
+to your Ory Network Project SDK URL. This is the URL that you would use to
+access the Ory API.
+
+If you have a custom domain for your Ory Network Project, you should use the
+custom domain for `NEXT_PUBLIC_ORY_SDK_URL`.
 
 ## Installation
 
 Run `npm install @ory/nextjs` or `yarn add @ory/nextjs` to install the package.
 
-## Examples
+### Next.js middleware for local development and preview deployments
+
+This package includes a middleware for the app and page router that enables
+local development and preview environments to work with Ory's cookie security
+model. When using the `@ory/nextjs` middleware, Ory Tunnel is not needed for
+development.
+
+Check the
+[app router](https://github.com/ory/elements/blob/main/examples/nextjs-app-router/middleware.ts)
+and
+[page router](https://github.com/ory/elements/blob/main/examples/nextjs-pages-router/middleware.ts)
+middleware example for more details.
+
+## Learn by example
 
-See the [examples](../../examples) directory for examples on how to use the
-package.
+See the [examples](https://github.com/ory/elements/blob/main/examples) directory
+for examples on how to use the package.
 
 ## Development
 
diff --git a/packages/nextjs/src/utils/rewrite.ts b/packages/nextjs/src/utils/rewrite.ts
index 50f14d9f1..85fb21e5b 100644
--- a/packages/nextjs/src/utils/rewrite.ts
+++ b/packages/nextjs/src/utils/rewrite.ts
@@ -13,12 +13,21 @@ export function rewriteUrls(
 ) {
   for (const [_, [matchPath, replaceWith]] of [
     // TODO load these dynamically from the project config
+
+    // Old AX routes
     ["/ui/recovery", config.override?.recoveryUiPath],
     ["/ui/registration", config.override?.registrationUiPath],
     ["/ui/login", config.override?.loginUiPath],
     ["/ui/verification", config.override?.verificationUiPath],
     ["/ui/settings", config.override?.settingsUiPath],
     ["/ui/welcome", config.override?.defaultRedirectUri],
+
+    // New AX routes
+    ["/recovery", config.override?.recoveryUiPath],
+    ["/registration", config.override?.registrationUiPath],
+    ["/login", config.override?.loginUiPath],
+    ["/verification", config.override?.verificationUiPath],
+    ["/settings", config.override?.settingsUiPath],
   ].entries()) {
     const match = joinUrlPaths(matchBaseUrl, matchPath || "")
     if (replaceWith && source.startsWith(match)) {
diff --git a/packages/nextjs/src/utils/sdk.test.ts b/packages/nextjs/src/utils/sdk.test.ts
index fb641f02b..c77b7249b 100644
--- a/packages/nextjs/src/utils/sdk.test.ts
+++ b/packages/nextjs/src/utils/sdk.test.ts
@@ -12,6 +12,7 @@ import {
 describe("orySdkUrl", () => {
   beforeEach(() => {
     delete process.env["NEXT_PUBLIC_ORY_SDK_URL"]
+    delete process.env["ORY_SDK_URL"]
   })
 
   it("should return NEXT_PUBLIC_ORY_SDK_URL without trailing slash", () => {
@@ -19,7 +20,18 @@ describe("orySdkUrl", () => {
     expect(orySdkUrl()).toBe("https://example.com")
   })
 
-  it("should throw error when NEXT_PUBLIC_ORY_SDK_URL is not set", () => {
+  it("should prioritize NEXT_PUBLIC_ORY_SDK_URL over ORY_SDK_URL", () => {
+    process.env["NEXT_PUBLIC_ORY_SDK_URL"] = "https://next-public.com/"
+    process.env["ORY_SDK_URL"] = "https://regular.com/"
+    expect(orySdkUrl()).toBe("https://next-public.com")
+  })
+
+  it("should use ORY_SDK_URL when NEXT_PUBLIC_ORY_SDK_URL is not set", () => {
+    process.env["ORY_SDK_URL"] = "https://regular.com/"
+    expect(orySdkUrl()).toBe("https://regular.com")
+  })
+
+  it("should throw error when neither NEXT_PUBLIC_ORY_SDK_URL nor ORY_SDK_URL is set", () => {
     expect(() => orySdkUrl()).toThrow(
       "You need to set environment variable `NEXT_PUBLIC_ORY_SDK_URL` to your Ory Network SDK URL.",
     )
@@ -28,20 +40,37 @@ describe("orySdkUrl", () => {
 
 describe("isProduction", () => {
   beforeEach(() => {
+    delete process.env["NEXT_PUBLIC_VERCEL_ENV"]
     delete process.env["VERCEL_ENV"]
+    delete process.env["NEXT_PUBLIC_NODE_ENV"]
     delete process.env["NODE_ENV"]
   })
 
+  it("should return true when NEXT_PUBLIC_VERCEL_ENV is production", () => {
+    process.env["NEXT_PUBLIC_VERCEL_ENV"] = "production"
+    expect(isProduction()).toBe(true)
+  })
+
   it("should return true when VERCEL_ENV is production", () => {
     process.env["VERCEL_ENV"] = "production"
     expect(isProduction()).toBe(true)
   })
 
+  it("should return true when NEXT_PUBLIC_NODE_ENV is production", () => {
+    process.env["NEXT_PUBLIC_NODE_ENV"] = "production"
+    expect(isProduction()).toBe(true)
+  })
+
   it("should return true when NODE_ENV is production", () => {
     process.env["NODE_ENV"] = "production"
     expect(isProduction()).toBe(true)
   })
 
+  it("should return true when VERCEL_ENV is prod", () => {
+    process.env["VERCEL_ENV"] = "prod"
+    expect(isProduction()).toBe(true)
+  })
+
   it("should return false when VERCEL_ENV and NODE_ENV are not production", () => {
     process.env["VERCEL_ENV"] = "development"
     process.env["NODE_ENV"] = "test"
@@ -56,9 +85,15 @@ describe("isProduction", () => {
 describe("guessPotentiallyProxiedOrySdkUrl", () => {
   beforeEach(() => {
     delete process.env["NEXT_PUBLIC_ORY_SDK_URL"]
+    delete process.env["ORY_SDK_URL"]
+    delete process.env["NEXT_PUBLIC_VERCEL_ENV"]
     delete process.env["VERCEL_ENV"]
+    delete process.env["NEXT_PUBLIC_NODE_ENV"]
     delete process.env["NODE_ENV"]
+    delete process.env["NEXT_PUBLIC_VERCEL_URL"]
     delete process.env["VERCEL_URL"]
+    delete process.env["NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL"]
+    delete process.env["VERCEL_PROJECT_PRODUCTION_URL"]
     delete process.env["__NEXT_PRIVATE_ORIGIN"]
   })
 
@@ -74,6 +109,39 @@ describe("guessPotentiallyProxiedOrySdkUrl", () => {
     expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://myapp.vercel.app")
   })
 
+  it("should use NEXT_PUBLIC_VERCEL_URL when available", () => {
+    process.env["VERCEL_ENV"] = "preview"
+    process.env["NEXT_PUBLIC_VERCEL_URL"] = "public-myapp.vercel.app"
+    expect(guessPotentiallyProxiedOrySdkUrl()).toBe(
+      "https://public-myapp.vercel.app",
+    )
+  })
+
+  it("should use VERCEL_PROJECT_PRODUCTION_URL in production for vercel.app domains", () => {
+    process.env["VERCEL_ENV"] = "production"
+    process.env["NEXT_PUBLIC_ORY_SDK_URL"] = "https://example.com/"
+    process.env["VERCEL_PROJECT_PRODUCTION_URL"] = "myapp.vercel.app"
+    expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://myapp.vercel.app")
+  })
+
+  it("should prioritize NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL", () => {
+    process.env["VERCEL_ENV"] = "production"
+    process.env["NEXT_PUBLIC_ORY_SDK_URL"] = "https://example.com/"
+    process.env["NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL"] =
+      "public-myapp.vercel.app"
+    process.env["VERCEL_PROJECT_PRODUCTION_URL"] = "myapp.vercel.app"
+    expect(guessPotentiallyProxiedOrySdkUrl()).toBe(
+      "https://public-myapp.vercel.app",
+    )
+  })
+
+  it("should ignore VERCEL_PROJECT_PRODUCTION_URL in production for non-vercel.app domains", () => {
+    process.env["VERCEL_ENV"] = "production"
+    process.env["NEXT_PUBLIC_ORY_SDK_URL"] = "https://example.com/"
+    process.env["VERCEL_PROJECT_PRODUCTION_URL"] = "custom-domain.com"
+    expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://example.com")
+  })
+
   it("should return __NEXT_PRIVATE_ORIGIN when __NEXT_PRIVATE_ORIGIN is set", () => {
     process.env["VERCEL_ENV"] = "preview"
     process.env["__NEXT_PRIVATE_ORIGIN"] = "https://private-origin/"
diff --git a/packages/nextjs/src/utils/sdk.ts b/packages/nextjs/src/utils/sdk.ts
index cca62630b..ae59f7ba2 100644
--- a/packages/nextjs/src/utils/sdk.ts
+++ b/packages/nextjs/src/utils/sdk.ts
@@ -1,12 +1,15 @@
 // Copyright © 2024 Ory Corp
 // SPDX-License-Identifier: Apache-2.0
 
-export function orySdkUrl() {
-  let baseUrl
+/**
+ * Gets environment variable, prioritizing the NEXT_PUBLIC_ prefixed version
+ */
+function getEnv(name: string): string | undefined {
+  return process.env[`NEXT_PUBLIC_${name}`] || process.env[name]
+}
 
-  if (process.env["NEXT_PUBLIC_ORY_SDK_URL"]) {
-    baseUrl = process.env["NEXT_PUBLIC_ORY_SDK_URL"]
-  }
+export function orySdkUrl() {
+  const baseUrl = getEnv("ORY_SDK_URL")
 
   if (!baseUrl) {
     throw new Error(
@@ -18,29 +21,26 @@ export function orySdkUrl() {
 }
 
 export function isProduction() {
-  return (
-    ["production", "prod"].indexOf(
-      process.env["VERCEL_ENV"] || process.env["NODE_ENV"] || "",
-    ) > -1
-  )
+  const env = getEnv("VERCEL_ENV") || getEnv("NODE_ENV") || ""
+  return ["production", "prod"].indexOf(env) > -1
 }
 
 export function guessPotentiallyProxiedOrySdkUrl(options?: {
   knownProxiedUrl?: string
 }) {
-  if (isProduction()) {
-    // In production, we use the production custom domain
-    return orySdkUrl()
-  }
-
-  if (process.env["VERCEL_ENV"]) {
+  if (getEnv("VERCEL_ENV")) {
     // We are in vercel
 
-    // The domain name of the generated deployment URL. Example: *.vercel.app. The value does not include the protocol scheme https://.
-    //
+    // The domain name of the generated deployment URL. Example: *.vercel.app
     // This is only available for preview deployments on Vercel.
-    if (!isProduction() && process.env["VERCEL_URL"]) {
-      return `https://${process.env["VERCEL_URL"]}`.replace(/\/$/, "")
+    if (!isProduction() && getEnv("VERCEL_URL")) {
+      return `https://${getEnv("VERCEL_URL")}`.replace(/\/$/, "")
+    }
+
+    const productionUrl = getEnv("VERCEL_PROJECT_PRODUCTION_URL") || ""
+    if (isProduction() && productionUrl.indexOf("vercel.app") > -1) {
+      // This is a production deployment on Vercel without a custom domain, so we use the vercel app domain.
+      return `https://${productionUrl}`.replace(/\/$/, "")
     }
 
     // This is sometimes set by the render server.
@@ -49,8 +49,12 @@ export function guessPotentiallyProxiedOrySdkUrl(options?: {
     }
   }
 
-  // Unable to figure out the SDK URL. Either because we are not using Vercel or because we are on a local machine.
-  // Let's try to use the window location.
+  if (isProduction()) {
+    // In production, we use the production custom domain
+    return orySdkUrl()
+  }
+
+  // Try to use window location
   if (typeof window !== "undefined") {
     return window.location.origin
   }


Reviews:
user: login: jonas-jonas
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-22T12:21:14Z
user: login: jonas-jonas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T20:14:05Z
user: login: aeneasr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T09:27:12Z
user: login: aeneasr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T10:25:49Z

Review comments:
diff_hunk: @@ -74,6 +109,34 @@ describe("guessPotentiallyProxiedOrySdkUrl", () => {
     expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://myapp.vercel.app")
   })
 
+  it("should use NEXT_PUBLIC_VERCEL_URL when available", () => {
+    process.env["VERCEL_ENV"] = "preview"
+    process.env["NEXT_PUBLIC_VERCEL_URL"] = "public-myapp.vercel.app"
+    expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://public-myapp.vercel.app")
+  })
+
+  it("should use VERCEL_PROJECT_PRODUCTION_URL in production for vercel.app domains", () => {
path: packages/nextjs/src/utils/sdk.test.ts
user: login: jonas-jonas
body: Is this actually true? If `VERCEL_ENV=production` we always use `ORY_SDK_URL`.
created_at: 2025-04-22T20:13:50Z
author_association: MEMBER
diff_hunk: @@ -74,6 +109,34 @@ describe("guessPotentiallyProxiedOrySdkUrl", () => {
     expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://myapp.vercel.app")
   })
 
+  it("should use NEXT_PUBLIC_VERCEL_URL when available", () => {
+    process.env["VERCEL_ENV"] = "preview"
+    process.env["NEXT_PUBLIC_VERCEL_URL"] = "public-myapp.vercel.app"
+    expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://public-myapp.vercel.app")
+  })
+
+  it("should use VERCEL_PROJECT_PRODUCTION_URL in production for vercel.app domains", () => {
path: packages/nextjs/src/utils/sdk.test.ts
user: login: aeneasr
body: I fixed it
created_at: 2025-04-23T09:27:12Z
author_association: MEMBER
diff_hunk: @@ -74,6 +109,34 @@ describe("guessPotentiallyProxiedOrySdkUrl", () => {
     expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://myapp.vercel.app")
   })
 
+  it("should use NEXT_PUBLIC_VERCEL_URL when available", () => {
+    process.env["VERCEL_ENV"] = "preview"
+    process.env["NEXT_PUBLIC_VERCEL_URL"] = "public-myapp.vercel.app"
+    expect(guessPotentiallyProxiedOrySdkUrl()).toBe("https://public-myapp.vercel.app")
+  })
+
+  it("should use VERCEL_PROJECT_PRODUCTION_URL in production for vercel.app domains", () => {
path: packages/nextjs/src/utils/sdk.test.ts
user: login: aeneasr
body: The code was broken and the test was right, I fixed it so tests are passing now. If we run on a vercel.app domain, we have to use the NextJS middleware or otherwise cookies won't work.
created_at: 2025-04-23T10:25:48Z
author_association: MEMBER

PR comments:
user: login: vercel[bot]
created_at: 2025-04-22T12:14:17Z
body: [vc]: #EYq0HgJ4+Cw5UIaqo0n63sse+3QnsTjEsVRJF7R9CD0=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJlbGVtZW50cy1uZXh0anMtYXBwLXJvdXRlciIsInJvb3REaXJlY3RvcnkiOiJleGFtcGxlcy9uZXh0anMtYXBwLXJvdXRlciIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9vcnkvZWxlbWVudHMtbmV4dGpzLWFwcC1yb3V0ZXIvMmlxRzZNUnZ2Q0dwUVd0VFZ4ZTJDMkNHVkNxcCIsInByZXZpZXdVcmwiOiJlbGVtZW50cy1uZXh0anMtYXBwLXJvdXRlci1naXQtZml4LTQ4Mi1vcnkudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJlbGVtZW50cy1uZXh0anMtYXBwLXJvdXRlci1naXQtZml4LTQ4Mi1vcnkudmVyY2VsLmFwcCJ9fSx7Im5hbWUiOiJlbGVtZW50cy1uZXh0anMtcGFnZXMtcm91dGVyIiwicm9vdERpcmVjdG9yeSI6ImV4YW1wbGVzL25leHRqcy1wYWdlcy1yb3V0ZXIiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vb3J5L2VsZW1lbnRzLW5leHRqcy1wYWdlcy1yb3V0ZXIvOW5uZTd4djZEOFFwdFBZaTJVcVdXZHpRcmpFQiIsInByZXZpZXdVcmwiOiJlbGVtZW50cy1uZXh0anMtcGFnZXMtcm91dGVyLWdpdC1maXgtNDgyLW9yeS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImVsZW1lbnRzLW5leHRqcy1wYWdlcy1yb3V0ZXItZ2l0LWZpeC00ODItb3J5LnZlcmNlbC5hcHAifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **elements-nextjs-app-router** | ✅ Ready ([Inspect](https://vercel.com/ory/elements-nextjs-app-router/2iqG6MRvvCGpQWtTVxe2C2CGVCqp)) | [Visit Preview](https://elements-nextjs-app-router-git-fix-482-ory.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/elements-nextjs-app-router-git-fix-482-ory.vercel.app?via=pr-comment-feedback-link) | Apr 23, 2025 10:35am |
| **elements-nextjs-pages-router** | ✅ Ready ([Inspect](https://vercel.com/ory/elements-nextjs-pages-router/9nne7xv6D8QptPYi2UqWWdzQrjEB)) | [Visit Preview](https://elements-nextjs-pages-router-git-fix-482-ory.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/elements-nextjs-pages-router-git-fix-482-ory.vercel.app?via=pr-comment-feedback-link) | Apr 23, 2025 10:35am |



author_association: NONE
user: login: changeset-bot[bot]
created_at: 2025-04-22T12:14:18Z
body: ###  ⚠️  No Changeset found

Latest commit: 6a3e3300e0743e705ee9cb36dede81d8025edac8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**



[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/ory/elements/new/fix-482?filename=.changeset/khaki-oranges-perform.md&value=---%0A%22%40fake-scope%2Ffake-pkg%22%3A%20patch%0A---%0A%0Afix%3A%20correctly%20guess%20vercel%20environment%0A)


author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-23T08:48:33Z
body: ## [Codecov](https://app.codecov.io/gh/ory/elements/pull/484?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ory) Report
Attention: Patch coverage is `95.00000%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 50.21%. Comparing base [(`f3fad4d`)](https://app.codecov.io/gh/ory/elements/commit/f3fad4d119de41e96ed28c17b752b8565d7ba04c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ory) to head [(`6a3e330`)](https://app.codecov.io/gh/ory/elements/commit/6a3e3300e0743e705ee9cb36dede81d8025edac8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ory).
> Report is 149 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #484      +/-   ##
==========================================
+ Coverage   42.43%   50.21%   +7.78%     
==========================================
  Files         136      153      +17     
  Lines        2008     2585     +577     
  Branches      288      387      +99     
==========================================
+ Hits          852     1298     +446     
- Misses       1149     1266     +117     
- Partials        7       21      +14     
```

| [Components](https://app.codecov.io/gh/ory/elements/pull/484/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ory) | Coverage Δ | |
|---|---|---|
| [@ory/elements-react](https://app.codecov.io/gh/ory/elements/pull/484/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ory) | `47.23% <ø> (+10.44%)` | :arrow_up: |
| [@ory/nextjs](https://app.codecov.io/gh/ory/elements/pull/484/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ory) | `63.03% <ø> (-2.95%)` | :arrow_down: |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: aeneasr
created_at: 2025-04-23T10:26:10Z
body: Right now, it's failing in the preview env with apparently a missing env var? So I need to figure out what's going on there
author_association: MEMBER
user: login: aeneasr
created_at: 2025-04-23T10:27:15Z
body: Ok, it was missing env vars in vercel.
author_association: MEMBER