Skip to content

getRequestHeaders is not a function in serverless environments (Netlify/Vercel) #384

Description

@crudbrain

Description

When deploying a TanStack Start application with @convex-dev/better-auth to serverless platforms (Netlify, Vercel), the application crashes at runtime with:

TypeError: getRequestHeaders is not a function
    at file:///var/task/.netlify/v1/functions/server.mjs:650:25
    at async getToken (file:///var/task/.netlify/v1/functions/server.mjs:671:19)
    at async Object.serverFn (file:///var/task/.netlify/v1/functions/server.mjs:732:14)

The build succeeds without errors, but the error occurs when visiting the deployed URL.

Environment

  • @convex-dev/better-auth: ^0.12.2
  • @tanstack/react-start: ^1.168.13
  • better-auth: ^1.6.11
  • convex: ^1.39.1
  • Deployment platforms: Netlify, Vercel

Root Cause

The issue is in @convex-dev/better-auth/dist/react-start/index.js at line 62:

const cachedGetToken = cache(async (opts) => {
    const { getRequestHeaders } = await import("@tanstack/react-start/server");
    const headers = getRequestHeaders();
    // ...
});

The dynamic import await import("@tanstack/react-start/server") fails to resolve correctly in serverless environments, returning a module object where getRequestHeaders is undefined.

This happens because:

  1. Serverless bundlers (Vite/Nitro) may not correctly resolve dynamic imports at runtime
  2. The module resolution in serverless environments differs from local development
  3. Tree-shaking or module externalization can cause the export to be missing

Reproduction

  1. Create a TanStack Start app with @convex-dev/better-auth
  2. Configure auth following the official guide
  3. Deploy to Netlify or Vercel
  4. Visit the deployed URL → Error occurs

Minimal auth-server.ts

import { convexBetterAuthReactStart } from "@convex-dev/better-auth/react-start";

export const {
    handler,
    getToken,
    fetchAuthQuery,
    fetchAuthMutation,
    fetchAuthAction,
} = convexBetterAuthReactStart({
    convexUrl: process.env.VITE_CONVEX_URL!,
    convexSiteUrl: process.env.VITE_CONVEX_SITE_URL!,
});

Minimal __root.tsx using getToken

import { createServerFn } from "@tanstack/react-start";
import { getToken } from "../lib/auth-server";

const getAuth = createServerFn({ method: "GET" }).handler(async () => {
    return await getToken();
});

export const Route = createRootRouteWithContext({
    beforeLoad: async (ctx) => {
        const token = await getAuth();
        // ...
    },
});

Workaround / Fix

Option 1: Patch the library (Recommended)

Use patch-package to replace the dynamic import with a static import:

File: patches/@convex-dev+better-auth+0.12.2.patch

diff --git a/node_modules/@convex-dev/better-auth/dist/react-start/index.js b/node_modules/@convex-dev/better-auth/dist/react-start/index.js
index 3a2c9f1..b7d8e2a 100644
--- a/node_modules/@convex-dev/better-auth/dist/react-start/index.js
+++ b/node_modules/@convex-dev/better-auth/dist/react-start/index.js
@@ -1,6 +1,7 @@
 import { stripIndent } from "common-tags";
 import { ConvexHttpClient } from "convex/browser";
 import { getToken } from "../utils/index.js";
+import { getRequestHeaders } from "@tanstack/react-start/server";
 import React from "react";
 // Caching supported for React 19+ only
 const cache = React.cache ||
@@ -59,7 +60,6 @@ const handler = (request, opts) => {
 export const convexBetterAuthReactStart = (opts) => {
     const siteUrl = parseConvexSiteUrl(opts.convexSiteUrl);
     const cachedGetToken = cache(async (opts) => {
-        const { getRequestHeaders } = await import("@tanstack/react-start/server");
         const headers = getRequestHeaders();
         const mutableHeaders = new Headers(headers);
         mutableHeaders.delete("content-length");

Add to package.json:

{
  "scripts": {
    "postinstall": "patch-package"
  },
  "devDependencies": {
    "patch-package": "^8.0.1"
  }
}

Option 2: Vite configuration

Also ensure these modules are bundled (not externalized) in vite.config.ts:

export default defineConfig({
    // ... other config
    ssr: {
        noExternal: [
            "@convex-dev/better-auth",
            "@tanstack/react-start",
            "@tanstack/react-start-server",
            "@tanstack/start-server-core",
            "h3-v2",
            "@tanstack/start-client-core",
            "@tanstack/react-start-client",
        ],
    },
});

Suggested Fix

Replace the dynamic import with a static import in the library source:

Current code:

const cachedGetToken = cache(async (opts) => {
    const { getRequestHeaders } = await import("@tanstack/react-start/server");
    const headers = getRequestHeaders();
    // ...
});

Suggested code:

import { getRequestHeaders } from "@tanstack/react-start/server";

const cachedGetToken = cache(async (opts) => {
    const headers = getRequestHeaders();
    // ...
});

Since getRequestHeaders is only used within server functions (which run server-side), a static import is safe and more reliable across different deployment targets.

Additional Context

  • The error only occurs in production/serverless builds, not in local development (npm run dev)
  • The issue affects both Netlify and Vercel deployments
  • The @tanstack/react-start/server export chain is: @tanstack/react-start/server@tanstack/react-start-server@tanstack/start-server-coreh3-v2
  • All these modules correctly export getRequestHeaders, but the dynamic import resolution fails at runtime in bundled serverless functions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions