Docusaurus
Environment Variables
There are 2 ways to get environment variables into a docusaurus site. The
easiest approach is through the build-time substitution directly into
docusaurus.config.ts. These values are encoded into the build output, and is
accessible by all the server-side generated code.
However the downside of this is that secrets are then put into the Cloudflare key-value bucket.
We prefer an alternative approach where environment variables are passed through the server side worker, and the worker can make use of the configuration for server-side operations, then inject client-side configuration into the HTML response.
Then when the React code is loaded into the browser, the frontend application
can read the client side configuration from globalThis.
This approach maintains the same logistical flow of secrets from orchestrator to server to client with non-Docusaurus applications. The main advantage of this is to avoid keeping sensitive variables in the build output. It's also possible to update the environment variables without needing to rebuild the static site, because the server can just inject new variables into the HTML response.
The exact way this is implemented changes depending on the situation. Here's one way:
- Create a
src/types.tsthat contains theClientConfigandServerConfig.
type ClientConfig = {
X: string;
};
type ServerConfig = ClientConfig & {};
export type { ClientConfig, ServerConfig };
- In the
worker.tsin CF, we need to extend theEnvwithServerConfig. And we need to also propagate theClientConfigto the client side using theHTMLRewriter.
export interface Env extends ServerConfig {
// CF Worker Sites automatically injects this to point to the KV namespace
__STATIC_CONTENT: string;
}
if (
!["/assets", "/files", "/images", "/fonts"].some((path) =>
reqURL.pathname.startsWith(path),
)
) {
if (response.headers.get("Content-Type")?.includes("text/html")) {
const config: ClientConfig = {
X: env.X,
};
response = new HTMLRewriter()
.on("head", {
element(element) {
element.prepend(
`<script>globalThis.config = ${JSON.stringify(config)};</script>`,
{ html: true },
);
},
})
.transform(response);
}
}
This will create a script tag at the top of the head which will be made
available to all HTML responses outside of special asset directories.
- Create a
useConfighook that will returnClientConfig | null.
import useIsBrowser from "@docusaurus/useIsBrowser";
const useConfig = (): ClientConfig | null => {
const isBrowser = useIsBrowser();
if (!isBrowser) return null;
if (globalThis.config == null) {
throw new Error("ClientConfig is missing from `globalThis`!");
}
return globalThis.config;
};
- In any component that requires the secrets, they will need to use the hook.
const ConfigDisplay = () => {
const config = useConfig();
if (config == null) {
return <p>Loading...</p>;
}
return <p>Config X: {config.X}</p>;
};
Note that during server side generation, it will place a <p>Loading...</p> and
will be replaced with the <p>Config X: {config.X}</p> once the deferred
Docusaurus scripts run. Alternatively you can also return null, however that
would result in UI flicker because the HTML structure is not the same.
The important thing to understand here is that the environment variables can only be used by client side code. In Docusaurus this requires the use of the escape hatches documented in https://docusaurus.io/docs/advanced/ssg#escape-hatches.