One Directive, One Whole Module: the Call That Cannot Exist
A helper imported from a client-marked file arrives as a reference, not a function. The runtime error and the neutral-module pattern that fixes it.
TL;DR
Error occurred on a Server Component page. The issue was caused by a client marker directive in a module file. Moving the helper function to a neutral module resolved the issue.
Where the error came from
The first 500 showed up on /budaya, a pillar page of a fictional city portal called KotaPortal. The stack trace was one confusing line: toVideoItems is not a function. The strange part: the function existed, the file existed, the import path had no typo, and on other pages that reached the same module through client components, everything worked.
The first guess pointed the wrong way. Suspicion fell on the body of toVideoItems: maybe a refactor had removed the export, or the mapper referenced something wrong. None of those scenarios checked out. The function was exported correctly from video-playlist.tsx, complete with its EntityVideoItem type.
Only one thing differed from the working usages: the /budaya page is a Server Component, and it called toVideoItems directly during server render. The plain helper was exported from a file whose very first line carries the client marker directive. Symptoms like this are easy to recognize in hindsight and hard to predict up front. The error message points at the call site, not at the source file. TypeScript stays silent too: from the type system's point of view the cross-file import is valid, because the problem is not types but where the code gets evaluated. The production build even passed right up until the page actually rendered, so for a while the API payload seemed like the likely culprit.
The directive marks the module, not the components
The wrong assumption: the directive only labels the interactive components in the file, while types, constants, and helpers stay neutral. In reality it works at the module level. The whole file and everything it imports joins the client module graph. In the Next.js documentation on the server and client boundary [1], the two separated graphs render into an RSC payload: the server produces references to Client Components plus serializable props, not function values.
The consequence: non-component exports of a client module cannot be called from the server side. The react.dev directive reference [2] states it plainly: values imported from a client-marked module into server code must be either a React component or a serializable prop value; any other use throws an exception. What the server page received was not the function, but an uncallable client reference. The "is not a function" message is just the visible symptom.
What makes this hard to spot: the same module can work in one place and fail in another. A module is evaluated on the server when imported by server code, and on the client when imported by client code. What decides is not the file's content but which graph imports it. Other pages that reach the module through the client path never see this error.
The fix: a neutral module without the directive
The solution was not changing the mapper but changing its address. EntityVideoItem and toVideoItems moved to video-items.ts, a plain TypeScript file with no directive at all.
// Before: the helper rode in a client-marked module
import { toVideoItems } from "./video-playlist"; // fails when called from the server
// After: neutral module, safe to import in both graphs
import { toVideoItems } from "./video-items";
A directive-free module can be evaluated in both graphs: the pillar page calls its helper directly on the server, and the client-side VideoPlaylist imports the type from the same place without duplication. After this import swap, the build and the component test suite went green again; not a single line inside the mapper changed.
This pattern narrows the directive's blast radius to where it belongs. The Next.js directive reference [3] reminds that the marker is only needed in files rendered directly by Server Components, not in every file containing client components. The remaining rule of thumb: mappers, types, and constants used across both graphs live in a neutral module; the client marker goes only on entry components that actually need state or event handlers. The next mistake becomes easier to diagnose. If "is not a function" appears again, check which graph is calling the function before suspecting its contents.
Sources
- Next.js Docs, The Server and Client Boundary, accessed 26 October 2026.
- react.dev, the directive reference, accessed 26 October 2026.
- Next.js Docs, directive reference, accessed 26 October 2026.