Runtime.HandlerNotFound: server.handler is undefined or not exported

I just upgraded to Remix v2, using the instructions and examples found here and, even with much additional fiddling, have been unable to get the server function working successfully (though sometimes I’ve encountered different errors).

Here’s the full stack trace:

Runtime.HandlerNotFound: server.handler is undefined or not exported
    at UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1122:15)
    at async start (file:///var/runtime/index.mjs:1282:23)
    at async file:///var/runtime/index.mjs:1288:1

My server.ts currently looks like this:

import { createRequestHandler } from "@netlify/remix-adapter";
import * as build from "@remix-run/dev/server-build";

const handler = createRequestHandler({
  build,
  mode: process.env.NODE_ENV,
});

export default handler;

although I get the same error if it looks like this:

export { handleRequest as default } from "@netlify/remix-adapter";

My site name is magenta-lebkuchen-df8108.

How can I debug this further?

I’d advise you to follow these docs: Remix on Netlify | Netlify Docs. Remix doesn’t use Netlify Functions anymore, it runs on Edge Functions as per my understanding.

Thanks for the reply!

All of Netlify’s documentation for Remix (including the one you linked) claims that Netlify supports both regular Netlify Functions as well as Edge Functions. Are those incorrect? I have also tried migrating to Remix v2 using Edge Functions, and immediately ran into runtime compatibility issues. I can try to work around these if absolutely necessary, but I’m not sure how resolvable they are.

Note that I’ve seen and perused (not merely browsed) both that page that you linked, as well as the code in the netlify/remix-tempate repo.

Here are some other relevant files from my project:

remix.config.mjs

import { config } from "@netlify/remix-adapter";

/** @type {import('@remix-run/dev').AppConfig} */
export default {
  config,
  serverBuildPath: ".netlify/functions-internal/server.js",
  appDirectory: "src/remix",
  serverModuleFormat: "cjs",
};

netlify.toml

[build]
  command = "npx convex deploy && npx remix build"
  publish = "public"

[build.environment]
  NPM_FLAGS = "--include=dev"

[dev]
  command = "npx remix dev"
  targetPort = 3000

[functions]
  directory = "src/netlify/functions/"

[[plugins]]
  package = "netlify-plugin-inline-source"

[[plugins]]
  package = "@sentry/netlify-build-plugin"

  [plugins.inputs]
    sentryOrg = "globecommerce"
    sentryProject = "globecommerce"

[[redirects]]
  from = "/mixpanel/*"
  to = "https://api.mixpanel.com/:splat"
  status = 200

  [redirects.headers]
    X-From = "Netlify"

[[redirects]]
  from = "/*"
  to = "/.netlify/functions/server"
  status = 200

[[headers]]
  for = "/build/*"

  [headers.values]
    "Cache-Control" = "public, max-age=31560000, immutable"

Note that I have set

  serverModuleFormat: "cjs",

in remix.config.mjs, while the default for the starter template is to set this value to esm (I don’t know why this would be an issue, but pointing it out just in case).

Another note is that it works fine in development using the Netlify CLI–it’s only in the production context (I assume it would be the same in staging/branch deploy environments as well, but I don’t have any set up) that I see this issue.

I have asked the devs about this and would let you know.

Appreciate it, looking forward to their response!

Here’s some more potentially useful data.

Today I’m developing on a different machine than usual, and on this machine I prefer to host my dev environment on GitHub Codespaces. I tried running the Remix dev server in this context, and found that

  1. When navigating to the port (8888) that Netlify said I should use to access the dev server, I saw the same error I reported above, but
  2. When navigating to the port that Remix said it was running the dev server on (3000), the app worked.

See these accompanying screenshots:

Hey @rjdellecese! I work on the team at Netlify that owns Functions & Edge Functions. Taking a look at this now. My first hunch is that it indeed has something to do with the serverModuleFormat field, gonna check that now.

Verified my hunch! You need to keep serverModuleFormat at esm (or unchanged), otherwise our bundling won’t be able to detect the function as being a V2 Function (see Netlify introduces new Functions capabilities). The error you’re seeing is what happens when a V2 function isn’t detected as one.

I recommend that in the code snippet you posted it looks like you need to do ...config instead of config, and remove the serverBuildPath and serverModuleFormat properties.

I’m curious, what’s the reason you set CJS in there? Maybe you can work around those things in a different way.

Hey @skn0tt, appreciate the help!

I did as you advised and switched the serverModuleFormat to esm, by deferring to the config provided by @netlify/remix-adapter. Same story with the serverBuildPath. This did require updating some imports in my project, but that turned out to be easy enough.

Here’s my remix.config.js following those changes:

import { config } from "@netlify/remix-adapter";

/** @type {import('@remix-run/dev').AppConfig} */
export default {
  config,
  appDirectory: "src/remix",
  serverDependenciesToBundle: ["@clerk/clerk-react"],
};

This has resolved the issue of serving the Netlify server function in development (at the port provided by Netlify—see my previous message for more details), but I’m now seeing a new problem in production—Netlify seems to not even see the server function.

Any clue why this might be?

It looks like the netlify/remix config isn’t taken into account, because you’re adding it under the config key, as opposed to spreading it into the config itself. Try making this change:

import { config } from "@netlify/remix-adapter";

/** @type {import('@remix-run/dev').AppConfig} */
export default {
- config,
+ ...config,
  appDirectory: "src/remix",
  serverDependenciesToBundle: ["@clerk/clerk-react"],
};

Let me know if that helped.

Ah goodness, of course—my mistake! I see you also pointed this out in a previous message too, sorry for missing it. If only TypeScript struct types didn’t always permit arbitrary additional fields, that wouldn’t have slipped by.

I’m now properly spreading the @netlify/remix-adapter config, and am seeing the following error in development (I have not tried to deploy to production):

Verified also that both of the versions of server.ts in my initial post produce this same error, now.

That’s an interesting error :thinking: It looks like it’s coming from the Remix Dev Server, not from the Netlify CLI. Have you searched the Remix GitHub? Maybe there’s an issue there about it.

If you provide me with a reproduction repository, I’m also happy to take a look and debug it. We don’t typically look at user code, but learning more about the Remix internals is always good :smiley: