Skip to main content

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:

  1. Create a src/types.ts that contains the ClientConfig and ServerConfig.
type ClientConfig = {
X: string;
};

type ServerConfig = ClientConfig & {};

export type { ClientConfig, ServerConfig };
  1. In the worker.ts in CF, we need to extend the Env with ServerConfig. And we need to also propagate the ClientConfig to the client side using the HTMLRewriter.
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.

  1. Create a useConfig hook that will return ClientConfig | 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;
};
  1. 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.