How to Fix: I’m building an admin library for nextjs, but i encounter this weird issue. When attempting to pass server actions through React Context in a library setting, encountering TypeError: adapter.create is not a function. This occurs when trying to use server actions in a client component that receives them through context.

7 min read

This error is a React Server Components boundary problem, not a random library bug. In this setup, a server action is created on the server, passed into a React Context provider, consumed by a client component, and then invoked from a package-based admin library. That breaks the serialization and runtime assumptions Next.js uses for server actions, which is why you end up with TypeError: adapter.create is not a function instead of a clean compile-time warning.

Understanding the Root Cause

In Next.js, server actions are not ordinary JavaScript functions. They are special references compiled by Next.js and wired into the server runtime. When used correctly, Next can recognize them in supported places such as Server Components, form action handlers, or explicitly supported props crossing the server/client boundary.

The problem in this issue is that the action is being passed through React Context inside a library setting. That adds two important complications:

  1. Context is not a supported transport layer for server action references. Even if a function appears to exist at runtime, it may no longer retain the internal metadata Next.js expects.
  2. Library boundaries can hide Next.js-specific compilation behavior. If your admin package exposes client components that receive action references indirectly, the consuming app and the package may disagree about what is a callable action versus what is just a plain value.

That is why the error mentions adapter.create. Somewhere in the call chain, the runtime expects a server action adapter object or transformed callable reference, but instead it receives a value that no longer matches the expected internal shape. The result is a low-level runtime failure rather than a friendly API error.

In short: server actions should not be tunneled through React Context in a reusable client library. Pass plain serializable data through context, and keep action invocation at a supported boundary.

Step-by-Step Solution

The most reliable fix is to stop passing server actions through context and instead use one of these patterns:

  • Submit to a server action via a form action.
  • Call a route handler from the client library.
  • Keep the action in the app layer and pass only serializable config into the library.

1. Remove server actions from context

If your code looks conceptually like this, it is the source of the bug:

'use client'

import { createContext, useContext } from 'react'

export const AdminContext = createContext({
  create: undefined
})

export function AdminProvider({ create, children }) {
  return (
    <AdminContext.Provider value={{ create }}>
      {children}
    </AdminContext.Provider>
  )
}

export function useAdmin() {
  return useContext(AdminContext)
}

If create is a server action, this is the unsafe pattern.

2. Replace direct action passing with an HTTP-based adapter

In a reusable admin library, the safest abstraction is usually an API adapter that talks to a route handler.

Create a route handler in the Next.js app:

// app/api/admin/create/route.ts
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const body = await request.json()

  // Do validation, auth, DB write, etc.
  const result = {
    ok: true,
    data: body
  }

  return NextResponse.json(result)
}

Then in your library client code, call the route instead of expecting a server action through context:

'use client'

export async function createRecord(payload: any) {
  const res = await fetch('/api/admin/create', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  })

  if (!res.ok) {
    throw new Error('Failed to create record')
  }

  return res.json()
}

Your context can now safely provide plain functions created on the client:

'use client'

import { createContext, useContext } from 'react'
import { createRecord } from './createRecord'

const AdminContext = createContext({
  create: createRecord
})

export function AdminProvider({ children }) {
  return (
    <AdminContext.Provider value={{ create: createRecord }}>
      {children}
    </AdminContext.Provider>
  )
}

export function useAdmin() {
  return useContext(AdminContext)
}

3. If you must use server actions, keep them at the app boundary

If your goal is to preserve server action benefits, define and use them in the app, then wire your UI around supported patterns.

// app/actions.ts
'use server'

export async function createAdminRecord(formData: FormData) {
  const title = formData.get('title')

  // Save to DB
  return { ok: true, title }
}

Use the action directly in a server-rendered boundary:

// app/page.tsx
import { createAdminRecord } from './actions'
import { AdminForm } from './AdminForm'

export default function Page() {
  return <AdminForm action={createAdminRecord} />
}

Then consume it in a supported client component pattern, typically with a form:

'use client'

export function AdminForm({ action }: { action: (formData: FormData) => Promise<any> }) {
  return (
    <form action={action}>
      <input name="title" placeholder="Title" />
      <button type="submit">Create</button>
    </form>
  )
}

The critical detail is this: do not store that action in context and then invoke it later from arbitrary library state. Use it at the boundary where Next.js expects it.

4. For a reusable admin library, pass descriptors instead of executable server functions

A better library API is often:

type ResourceConfig = {
  createEndpoint: string
  updateEndpoint: string
  deleteEndpoint: string
}

Then your library handles generic client-side fetch logic, while the host app owns the server implementation.

'use client'

import { createContext, useContext } from 'react'

type AdminConfig = {
  createEndpoint: string
}

const AdminContext = createContext<AdminConfig | null>(null)

export function AdminProvider({ config, children }: { config: AdminConfig, children: React.ReactNode }) {
  return <AdminContext.Provider value={config}>{children}</AdminContext.Provider>
}

export function useAdmin() {
  const ctx = useContext(AdminContext)
  if (!ctx) throw new Error('AdminContext missing')
  return ctx
}

And in a consumer:

'use client'

import { useAdmin } from './context'

export function CreateButton() {
  const { createEndpoint } = useAdmin()

  async function handleClick() {
    await fetch(createEndpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: 'Test' })
    })
  }

  return <button onClick={handleClick}>Create</button>
}

5. Check package boundaries in your monorepo

Because the reproduction lives in a library + app setup, also verify these points:

  • The library does not accidentally bundle server-only code into a client component.
  • Files using hooks or browser APIs are marked with ‘use client’.
  • Files containing server actions are not re-exported through client entrypoints.
  • Your package build does not strip or relocate code in a way that confuses Next.js compilation.

A good rule is: the app owns server actions; the library owns client UI and serializable configuration.

Common Edge Cases

1. Re-exporting actions from shared packages

Even if a server action is defined correctly, re-exporting it through a package barrel file or mixed server/client module can break how Next recognizes it. Keep server action definitions close to the app layer.

2. Mixing server-only modules into client code

If your library context or adapter imports database code, Node-only utilities, or server actions directly, Next may compile the module incorrectly or fail at runtime. Split server-only and client-only modules aggressively.

3. Assuming all functions can cross context boundaries

Normal client functions can go through context. Server actions are special references, not generic callbacks. Treating them like plain functions is what triggers this class of bug.

4. Custom adapters that expect a specific runtime shape

The adapter.create failure suggests your library or dependency expects an adapter object with a callable create method. If the value came from a server action reference that was transformed or lost during transport, the property may exist in TypeScript types but fail at runtime.

5. Using onClick instead of supported action patterns

Even if an action reference reaches a client component, invoking it from an arbitrary onClick inside a library can be less reliable than using a supported form action workflow or route handler call.

FAQ

Can I ever pass a server action as a prop to a client component?

Yes, but only in supported Next.js patterns. The common safe case is passing an action into a component that uses it directly in a form action. Passing it through React Context in a reusable library is where things become fragile and unsupported.

Why does the error say adapter.create is not a function instead of mentioning server actions?

Because the failure happens downstream at runtime. By the time the bad value is consumed, the original problem is no longer obvious. Next.js internals or your abstraction layer expect a transformed callable object, but receive something else.

What is the best architecture for an admin library in Next.js?

Use the library for client UI, schemas, config, and fetch helpers. Keep server actions, authentication, database access, and route handlers in the host Next.js app. If you need generic mutations, expose endpoint-based adapters or app-defined callbacks that remain purely client-side.

If you want this issue resolved cleanly in the referenced repository, the practical fix is to remove server action values from context, move mutation logic behind route handlers or direct app-level action boundaries, and let the admin package consume only serializable configuration or client-callable adapters. That aligns the design with how Next.js expects server/client boundaries to work and eliminates the adapter.create runtime failure.

Leave a Reply

Your email address will not be published. Required fields are marked *