T
TanStack5mo ago
rival-black

I don't understand env variables in Vite

Everything works on prod but the moment I pnpm build && pnpm start locally I get this error.
[request error] [unhandled] [GET] http://localhost:3000/_server/src_routes_root_tsx--getUser_createServerFn_handler?payload=%7B%22data%22%3A%7B%22%24undefined%22%3A0%7D%2C%22context%22%3A%7B%7D%7D&createServerFn
LibsqlError: URL_INVALID: The URL 'undefined' is not in a valid format
at parseUri (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/core/lib-esm/uri.js:9:15)
... 8 lines matching cause stack trace ...
at async Object.callAsync (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:4026:16) {
cause: LibsqlError: URL_INVALID: The URL 'undefined' is not in a valid format
at parseUri (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/core/lib-esm/uri.js:9:15)
at expandConfig (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/core/lib-esm/config.js:25:17)
at createClient (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/client/lib-esm/node.js:11:26)
at file:///home/barsi/dev/senatoriables/.output/server/chunks/build/auth-YWrqIeVg.mjs:165:2495
at ModuleJob.run (node:internal/modules/esm/module_job:271:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:578:26)
at async ce$1 (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:5007:11)
at async Object.ie$1 [as handler] (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:4994:10)
at async file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:1587:19
at async Object.callAsync (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:4026:16) {
code: 'URL_INVALID',
rawCode: undefined,
[cause]: undefined
},
statusCode: 500,
fatal: false,
unhandled: true,
statusMessage: undefined,
data: undefined
}
{
error: true,
url: 'http://localhost:3000/_server/src_routes_root_tsx--getUser_createServerFn_handler?payload=%7B%22data%22%3A%7B%22%24undefined%22%3A0%7D%2C%22context%22%3A%7B%7D%7D&createServerFn',
statusCode: 500,
statusMessage: 'Server Error',
message: 'Server Error',
routerCode: 'BEFORE_LOAD'
}
[request error] [unhandled] [GET] http://localhost:3000/_server/src_routes_root_tsx--getUser_createServerFn_handler?payload=%7B%22data%22%3A%7B%22%24undefined%22%3A0%7D%2C%22context%22%3A%7B%7D%7D&createServerFn
LibsqlError: URL_INVALID: The URL 'undefined' is not in a valid format
at parseUri (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/core/lib-esm/uri.js:9:15)
... 8 lines matching cause stack trace ...
at async Object.callAsync (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:4026:16) {
cause: LibsqlError: URL_INVALID: The URL 'undefined' is not in a valid format
at parseUri (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/core/lib-esm/uri.js:9:15)
at expandConfig (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/core/lib-esm/config.js:25:17)
at createClient (file:///home/barsi/dev/senatoriables/.output/server/node_modules/@libsql/client/lib-esm/node.js:11:26)
at file:///home/barsi/dev/senatoriables/.output/server/chunks/build/auth-YWrqIeVg.mjs:165:2495
at ModuleJob.run (node:internal/modules/esm/module_job:271:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:578:26)
at async ce$1 (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:5007:11)
at async Object.ie$1 [as handler] (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:4994:10)
at async file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:1587:19
at async Object.callAsync (file:///home/barsi/dev/senatoriables/.output/server/chunks/nitro/nitro.mjs:4026:16) {
code: 'URL_INVALID',
rawCode: undefined,
[cause]: undefined
},
statusCode: 500,
fatal: false,
unhandled: true,
statusMessage: undefined,
data: undefined
}
{
error: true,
url: 'http://localhost:3000/_server/src_routes_root_tsx--getUser_createServerFn_handler?payload=%7B%22data%22%3A%7B%22%24undefined%22%3A0%7D%2C%22context%22%3A%7B%7D%7D&createServerFn',
statusCode: 500,
statusMessage: 'Server Error',
message: 'Server Error',
routerCode: 'BEFORE_LOAD'
}
So it seems it's an issue with getUser, which goes like this
const getUser = createServerFn({ method: "GET" }).handler(async () => {
const { headers } = getWebRequest()!;
const session = await auth.api.getSession({ headers });

return session?.user || null;
});
const getUser = createServerFn({ method: "GET" }).handler(async () => {
const { headers } = getWebRequest()!;
const session = await auth.api.getSession({ headers });

return session?.user || null;
});
since its a libsql issue it has something to do with auth hooking into the database
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin } from "better-auth/plugins";

import { db } from "./db";

export const auth = betterAuth({
advanced: {
cookiePrefix: "senatoriables",
},
baseURL: process.env.VITE_BASE_URL,
database: drizzleAdapter(db, {
provider: "sqlite",
}),
}
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin } from "better-auth/plugins";

import { db } from "./db";

export const auth = betterAuth({
advanced: {
cookiePrefix: "senatoriables",
},
baseURL: process.env.VITE_BASE_URL,
database: drizzleAdapter(db, {
provider: "sqlite",
}),
}
I go to db which is the one using the libsql client
import { type Client, createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import * as schema from "./schema";

const client = createClient({
url: process.env.DATABASE_URL as string,
authToken: process.env.DATABASE_AUTH_TOKEN as string,
});

export const db = drizzle(client, { schema, casing: "snake_case" });
import { type Client, createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import * as schema from "./schema";

const client = createClient({
url: process.env.DATABASE_URL as string,
authToken: process.env.DATABASE_AUTH_TOKEN as string,
});

export const db = drizzle(client, { schema, casing: "snake_case" });
i just dont see anything wrong with how im approaching things, but I get the error only on prod and dev just works fine
21 Replies
deep-jade
deep-jade5mo ago
since its only on prod how are you deploying
rival-black
rival-blackOP5mo ago
However, if I change the db instance's env variables to use import.env and prefix the env variables with VITE_ it seems to work
const client = createClient({
url: import.meta.env.VITE_DATABASE_URL as string,
authToken: import.meta.env.VITE_DATABASE_AUTH_TOKEN as string,
});

export const db = drizzle(client, { schema, casing: "snake_case" });
const client = createClient({
url: import.meta.env.VITE_DATABASE_URL as string,
authToken: import.meta.env.VITE_DATABASE_AUTH_TOKEN as string,
});

export const db = drizzle(client, { schema, casing: "snake_case" });
i'm trying it locally atm with pnpm buld && pnpm start
deep-jade
deep-jade5mo ago
i see
rival-black
rival-blackOP5mo ago
this way I now get access to my DB, but i thought import.meta.env was for client side env variables?
deep-jade
deep-jade5mo ago
well DONT do that yeah that gets leaked to client
rival-black
rival-blackOP5mo ago
im absolutely confused atm
deep-jade
deep-jade5mo ago
those are only for public in ur server fn i would try just printing out env vars and see if they are set
rival-black
rival-blackOP5mo ago
will try that out, 1 sec it does not, on pnpm build && pnpm start
rival-black
rival-blackOP5mo ago
No description
rival-black
rival-blackOP5mo ago
it does exist on dev mode
No description
deep-jade
deep-jade5mo ago
what is package json lemme see the scripts
rival-black
rival-blackOP5mo ago
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start",
"lint": "eslint .",
"format": "prettier --write .",
"db": "drizzle-kit",
"db:studio": "drizzle-kit studio",
"db:push": "drizzle-kit push --config=drizzle.config.ts",
"ui": "pnpm dlx shadcn@canary",
"auth": "pnpm dlx @better-auth/cli@latest",
"auth:generate": "pnpm dlx @better-auth/cli@latest generate --y --output ./src/lib/server/schema/auth.schema.ts && prettier --write ./src/lib/server/schema/auth.schema.ts",
"deps": "pnpm dlx npm-check-updates@latest --interactive --format group"
},
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start",
"lint": "eslint .",
"format": "prettier --write .",
"db": "drizzle-kit",
"db:studio": "drizzle-kit studio",
"db:push": "drizzle-kit push --config=drizzle.config.ts",
"ui": "pnpm dlx shadcn@canary",
"auth": "pnpm dlx @better-auth/cli@latest",
"auth:generate": "pnpm dlx @better-auth/cli@latest generate --y --output ./src/lib/server/schema/auth.schema.ts && prettier --write ./src/lib/server/schema/auth.schema.ts",
"deps": "pnpm dlx npm-check-updates@latest --interactive --format group"
},
deep-jade
deep-jade5mo ago
app config ts? do u have const env = loadEnv(process.env.NODE_ENV, process.cwd(), ""); thats the only thing i can think of
rival-black
rival-blackOP5mo ago
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "@tanstack/react-start/config";
import tsConfigPaths from "vite-tsconfig-paths";

export default defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ["./tsconfig.json"],
}),
tailwindcss(),
],
},

// https://react.dev/learn/react-compiler
react: {
babel: {
plugins: [
[
"babel-plugin-react-compiler",
{
target: "19",
},
],
],
},
},

tsr: {
// https://github.com/TanStack/router/discussions/2863#discussioncomment-12458714
appDirectory: "./src",
},

server: {
// https://tanstack.com/start/latest/docs/framework/react/hosting#deployment
// preset: "netlify",
},
});
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "@tanstack/react-start/config";
import tsConfigPaths from "vite-tsconfig-paths";

export default defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ["./tsconfig.json"],
}),
tailwindcss(),
],
},

// https://react.dev/learn/react-compiler
react: {
babel: {
plugins: [
[
"babel-plugin-react-compiler",
{
target: "19",
},
],
],
},
},

tsr: {
// https://github.com/TanStack/router/discussions/2863#discussioncomment-12458714
appDirectory: "./src",
},

server: {
// https://tanstack.com/start/latest/docs/framework/react/hosting#deployment
// preset: "netlify",
},
});
where do I add that?
genetic-orange
genetic-orange5mo ago
What about: 1: npm i dotenv 2: Add import 'dotenv/config' to entry-server (to prevent dotenv treeshaking at build) 3: Add import 'dotenv/config' after the build in the top of .output/index.mjs 4: Copy .env file to .output/server/
variable-lime
variable-lime5mo ago
The start script does not read the .env file and is not meant to. In this other discussion you can see how to make it work without additional code/tools https://discord.com/channels/719702312431386674/1361214519459971215
genetic-orange
genetic-orange5mo ago
nice one just for info, using
export $(grep -v '^#' server/.env | xargs)
export $(grep -v '^#' server/.env | xargs)
with a .env file originated from Windows, all values had a "\r" on the end Which as to do with line ending character in Windows vs Linux It can be fixed e.g. in VS code: 1: Look at the bottom-right of the window — you'll probably see: CRLF 2: Click on that → a dropdown will appear 3: Select: LF (Unix)
rival-black
rival-blackOP5mo ago
using the method used in the article mentioned on this thread! added this line to my package.json
"start:node": "node -r dotenv/config .output/server/index.mjs",
"start:node": "node -r dotenv/config .output/server/index.mjs",
jolly-crimson
jolly-crimson5mo ago
Hi there! I ran into this issue as well, but this solution didn't work for me when deploying. I've tried on both Vercel and Cloudflare, but the issue is that it doesn't use the start command from the package file. And I did check that my environment variables were correctly set in the hosting provider. I don't see anything regarding this in the TanStack docs or examples (even though they do have .env files). The only valid example I guess is the tanstack.com site itself, which is using Netlify.
variable-lime
variable-lime5mo ago
>And I did check that my environment variables were correctly set in the hosting provider. if you set them in the provider dashboard they should just work, regardless of how the project is started
jolly-crimson
jolly-crimson5mo ago
Yeah that's what I was thinking too, and that's why I'm confused. I'll try with an unrelated project to check if I get different results. Ok so just to keep this updated for other people running into this: I'm pretty sure that there's an issue in my codebase, but I can't figure out where. In a separate sample project, it does work well with all the server presets (cloudflare, node, vercel). The environment variables are correctly exposed and can be read everywhere (API routes, server functions, and lib/utils files). In my case, for Cloudflare, I ended up adding this compatibility flag to the wrangler.toml file: nodejs_compat_populate_process_env (based on this doc: https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-auto-populating-processenv). This way, all my env var are correctly populated. I think it's an issue somewhere in Nitro/unenv and Cloudflare, but that's all I can guess 🤷

Did you find this page helpful?