kit: $env/dynamic/public becomes $env/static/public for pre-rendered pages, breaking app and exposing information
Describe the bug
The docs for $env/dynamic/public
state:
This module provides access to runtime environment variables, as defined by the platform you’re running on.
For pre-rendered pages though, this is incorrect and misleading. They actually behave more like $env/static/public
with values coming from build-time .env
files used. Not only does this risk exposing information that wasn’t intended to be exposed but the behavior of the app becomes indeterminate because the values used in the app depend on the order of page navigation.
As an example, I’m using $env/dynamic/public
to read Firebase config from runtime environment values set in Google Cloud Run. Every so often Firebase auth on the client would fail, but a page refresh would always fix it. It depended on which page was initially viewed and exposed development-use configuration that was not intended to be exposed.
Reproduction
See https://github.com/CaptainCodeman/svelte-kit-10008
Logs
No response
System Info
System:
OS: macOS 13.3.1
CPU: (6) x64 Intel(R) Core(TM) i5-8500B CPU @ 3.00GHz
Memory: 36.28 MB / 32.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 18.10.0 - ~/Library/pnpm/node
npm: 8.19.2 - ~/Library/pnpm/npm
Browsers:
Brave Browser: 106.1.44.112
Chrome: 113.0.5672.126
Chrome Canary: 115.0.5786.0
Firefox: 111.0.1
Safari: 16.4
Safari Technology Preview: 16.4
npmPackages:
@sveltejs/adapter-node: ^1.2.4 => 1.2.4
@sveltejs/kit: ^1.18.0 => 1.18.0
svelte: ^3.59.1 => 3.59.1
vite: ^4.3.8 => 4.3.8
Severity
annoyance (had to disable pre-rendered pages for correct functionality)
Additional Information
Maybe related to https://github.com/sveltejs/kit/issues/8946
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 2
- Comments: 15 (14 by maintainers)
Thinking out loud:
modulepreload
) and cached forever (since they are immutable). If we instead imported those values from a module, then it couldn’t be part of the build (obviously) so would have to be an endpoint, as @benmccann says. Not only would this mean less efficient chunking, but the dynamic module couldn’t be cached immutably, so at minimum you’d have to wait for a 304 response for that module on every single page load before you could boot the app. That would be bad. The current approach is very deliberate$env/dynamic/public
on navigation — it would be weird if it changed value during the session (unless a new version of the app is deployed while the session is ongoing, but that’s a whole other story)$env/dynamic/public
populated with build-time variables, but equally you don’t want to land on a dynamically rendered page and have it be populated with request-time variables that are unexpectedly used on a prerendered page that you later navigate to$env/static/public
, which is more efficient anyway.$env/dynamic/public
during prerenderingThat solves the easy part of the problem. The trickier part is subsequently populating
$env/dynamic/public
with request-time variables.Since a prerendered page could access
$env/dynamic/public
before a navigation (in an event handler, or even in some browser-only code that runs immediately upon rendering), it doesn’t seem sufficient to e.g. smuggle the env vars into the data for the next request.Which means that in the prerendered case I don’t think we have a choice other than to serve env vars dynamically, from a
${base}/_env.js
module (configurable viaconfig.kit.env.publicModule
). This is a bummer — as described, this undermines the performance characteristics of prerendering by forcing you to make a request to a (potentially distant) origin server before you can hydrate an otherwise-fully-prerendered page.Mitigations:
adapter-static
case, the module would also be ‘prerendered’, so even though you have to wait for a 304, you’re at least dealing with a CDN rather than an origin server$env/dynamic/public
is never imported in the app. this would happen ‘for free’ if that module basically just re-exports from../../_env.js
if no request-time env vars exist in the HTMLmodulepreload
for${base}/_env.js
into the prerendered HTML if$env/dynamic/public
is imported somewhere in the appIt’s not really whether there should be a pre-render option or not, I don’t think there is any case to be made for it - unless the app is built on the server it will run on (which would be a bit weird) or you happen to have a local
.env
file that matches production, the values will always be wrong if they are pre-rendered.Pre-rendering stops the “dynamic” part doing what it says on the tin. The purpose of dynamic should always be to reflect that values set on the server, not anything that is compiled into the app, otherwise you’d just use the static option.