Next.js Discord

Discord Forum

How do I initialise an SDK once as my server starts, and keep it in memory in between requests?

Unanswered
Spectacled bear posted this in #help-forum
Open in Discord
Spectacled bearOP
The problem

I'm using a library which requires some initialisation which is slow enough that I don't want it to run on every request. I want to initialise the SDK once only, as the server starts up, and keep it in memory forever, so that when requests come into the server the SDK doesn't need to be initialised again.

The obvious solution (which doesn't seem to work)

Usually I would just instantiate it at module scope and import it wherever I need it:
// src/someLibrarySdk.ts
import SomeSdk from 'some-library'

export const someSdk = new SomeSdk();


One complication is that I need to fetch some remote config in order to initialise the SDK, so it's more like:

// src/someLibrarySdk.ts
import SomeSdk from 'some-library'

export let someSdk;

const init = async () => {
  const config = await fetchRemoteConfig();
  console.log('initialising SDK');
  someSdk = new SomeSdk();
}
// Eagerly initialise
init();


Where it goes really wrong

It seems like next.js does weird stuff with the module system. When I run my app locally (either with next dev, or with next build && next start), I don't see the above console.log output when I expect:

- It doesn't run at server start
- It does run on every single request to the server

Additionally, if I log out the value of someSdk, it is often undefined, so it seems like the value of that variable is being cleared from request to request. It's not being kept in memory.

-----------

This seems like such a basic thing but I've been fiddling with it for hours and can't seem to get it to work. I've tried having an instrumentation.ts file which exports a register function, and that doesn't seem to help either. I still get weird results where the module-level code doesn't run when I expect (and does run whenI don't expect).

Is there any standard pattern for running some init code once on the server, and keeping stuff in memory from request to request?

I'm using next@13.3.1

1 Reply

Spectacled bearOP
I've just tried a variation on this where I split out the async config fetching and run just that from register and it still doesn't work.

// src/instrumentation.ts
export async function register() {
  const { preFetchRemoteConfig } = await import('./someLibrarySdk.ts')
  await preFetchRemoteConfig()
}

// src/someLibrarySdk.ts
let remoteConfig: string | undefined
export const preFetchRemoteConfig = async () => {
  remoteConfig = await fetchRemoteConfig();
}

export const getSomeSdk = () => {
  // This always shows remoteConfig as `undefined` 😩
  console.log('Creating SDK with already-fetched config', remoteConfig);
  return new SomeSdk(remoteConfig);
}

// src/_app.tsx
export default function MyApp = ({ Component, pageProps }) => {
  const someSdk = getSomeSdk();
  return (
    <SomeContextProvider sdkClient={someSdk}>
      <Component {...pageProps} />
    </SomeContextProvider>
  );
}


When I run this code:
- ✅ I can see the prefetch run as I start the server, during "registration"
- ❓ I can see the prefetch run again as the first request comes into the server and the route gets compiled. Maybe this is just local dev server behaviour that it re-runs the registration upon route compilation?
- ✅ I can see that getSomeSdk does not run until after the data file has been pre-fetched and supposedly set into memory
- ❌ And yet, remoteConfig is undefined. Somehow, in between the registration finishing, and the component rendering, the module-scoped variable loses its value 🙁
- ❌ If I refresh the page, the registration does not run again, and remoteConfig continues to be undefined during the component rendering