npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tnnquang/context-bridge

v1.0.0

Published

A connector to get data from Server Component and use it in Client Component instead of React Context. This is a super lightweight library, no need to use Tanstack Query or similar libraries.

Readme

Context Bridge

A lightweight library to bridge data from React Server Components to Client Components without using React Context. Perfect for Next.js App Router and other frameworks supporting React Server Components.

Why Context Bridge?

  • Zero Runtime Overhead: Data is serialized once during SSR and hydrated on the client
  • Type-Safe: Full TypeScript support with proper type inference
  • Simple API: Just two functions to learn
  • No Extra Dependencies: Works with React 19+
  • RSC Compatible: Designed specifically for React Server Components

Installation

npm install @tnnquang/context-bridge
# or
pnpm add @tnnquang/context-bridge
# or
yarn add @tnnquang/context-bridge

Peer Dependencies

  • React 19.0.0 or higher

Quick Start

Step 1: Create Your Bridge

Create a file to define your bridge (e.g., lib/user-bridge.ts):

import { createBridgeProvider } from '@tnnquang/context-bridge/server';
import { createBridgeHook } from '@tnnquang/context-bridge/client';

type User = {
  id: string;
  name: string;
  email: string;
};

// Create provider for Server Component
export const UserProvider = createBridgeProvider<User>('user');

// Create hook for Client Component
export const useUser = createBridgeHook<User>('user', {
  id: '',
  name: 'Guest',
  email: '',
});

Step 2: Add BridgeSerializer to Root Layout

In your root layout (e.g., app/layout.tsx), add the BridgeSerializer component at the end of the <body> tag:

import { BridgeSerializer } from '@tnnquang/context-bridge/server';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <BridgeSerializer />
      </body>
    </html>
  );
}

Step 3: Provide Data in Server Component

Use the Provider in your Server Component to pass data:

import { UserProvider } from '@/lib/user-bridge';

// This is a Server Component
export default async function Page() {
  // Fetch data on the server
  const user = await fetchUser();

  return (
    <UserProvider data={user}>
      <UserProfile />
      <UserDashboard />
    </UserProvider>
  );
}

Step 4: Consume Data in Client Component

Use the hook in your Client Component to access the data:

'use client';

import { useUser } from '@/lib/user-bridge';

export function UserProfile() {
  const user = useUser();

  return (
    <div>
      <h1>Welcome, {user.name}!</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

Advanced Usage

Multiple Bridges

You can create multiple bridges for different data types:

// lib/bridges.ts
import { createBridgeProvider } from '@tnnquang/context-bridge/server';
import { createBridgeHook } from '@tnnquang/context-bridge/client';

// User bridge
type User = { id: string; name: string };
export const UserProvider = createBridgeProvider<User>('user');
export const useUser = createBridgeHook<User>('user', { id: '', name: '' });

// Theme bridge
type Theme = { mode: 'light' | 'dark'; primaryColor: string };
export const ThemeProvider = createBridgeProvider<Theme>('theme');
export const useTheme = createBridgeHook<Theme>('theme', {
  mode: 'light',
  primaryColor: '#000000',
});

// Settings bridge
type Settings = { language: string; notifications: boolean };
export const SettingsProvider = createBridgeProvider<Settings>('settings');
export const useSettings = createBridgeHook<Settings>('settings', {
  language: 'en',
  notifications: true,
});

Nested Providers

Providers can be nested without any issues:

export default async function Page() {
  const user = await fetchUser();
  const theme = await fetchTheme();
  const settings = await fetchSettings();

  return (
    <UserProvider data={user}>
      <ThemeProvider data={theme}>
        <SettingsProvider data={settings}>
          <Dashboard />
        </SettingsProvider>
      </ThemeProvider>
    </UserProvider>
  );
}

Complex Data Types

Context Bridge supports any JSON-serializable data:

type ComplexData = {
  users: User[];
  metadata: {
    timestamp: string;
    version: number;
  };
  settings: Record<string, any>;
  flags: boolean[];
};

export const DataProvider = createBridgeProvider<ComplexData>('complex-data');
export const useComplexData = createBridgeHook<ComplexData>('complex-data', {
  users: [],
  metadata: { timestamp: '', version: 0 },
  settings: {},
  flags: [],
});

API Reference

Server-Side API

createBridgeProvider<T>(bridgeId: string)

Creates a Provider component for Server Components.

Parameters:

  • bridgeId: A unique string identifier for this bridge
  • T: TypeScript type for the data

Returns: A Provider component with props:

  • data: T - The data to pass to client components
  • children: ReactNode - Child components

Example:

const UserProvider = createBridgeProvider<User>('user');

BridgeSerializer

A component that serializes all bridge data into a script tag.

Usage:

import { BridgeSerializer } from '@tnnquang/context-bridge/server';

// Place at the end of <body> in root layout
<body>
  {children}
  <BridgeSerializer />
</body>

Client-Side API

createBridgeHook<T>(bridgeId: string, defaultValue: T)

Creates a hook for Client Components to access bridge data.

Parameters:

  • bridgeId: Must match the bridgeId used in createBridgeProvider
  • defaultValue: Default value if data is not available

Returns: A hook that returns data of type T

Example:

const useUser = createBridgeHook<User>('user', {
  id: '',
  name: 'Guest',
});

Best Practices

1. Use Consistent Bridge IDs

Always use the same bridgeId for both provider and hook:

// Good
const bridgeId = 'user-data';
export const UserProvider = createBridgeProvider(bridgeId);
export const useUser = createBridgeHook(bridgeId, defaultUser);

// Bad - Different IDs
export const UserProvider = createBridgeProvider('user');
export const useUser = createBridgeHook('userData', defaultUser); // Won't work!

2. Provide Meaningful Default Values

Default values are used during initial hydration and when data is missing:

// Good - Provides safe defaults
const useUser = createBridgeHook<User>('user', {
  id: '',
  name: 'Guest',
  email: '',
  isAuthenticated: false,
});

// Bad - Might cause runtime errors
const useUser = createBridgeHook<User>('user', null as any);

3. Keep Data JSON-Serializable

Only pass data that can be serialized to JSON:

// Good
const data = {
  id: '123',
  name: 'John',
  createdAt: new Date().toISOString(), // Convert Date to string
};

// Bad - These will cause errors
const data = {
  onClick: () => {}, // Functions cannot be serialized
  element: <div />, // React elements cannot be serialized
  circularRef: obj, // Circular references will throw
};

4. Organize Bridges in Separate Files

Keep your bridges organized:

lib/
  bridges/
    user-bridge.ts
    theme-bridge.ts
    settings-bridge.ts
    index.ts  // Export all bridges

5. Use TypeScript for Type Safety

Always define proper types for your data:

// Define your types
type User = {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
};

// Use them in your bridge
export const UserProvider = createBridgeProvider<User>('user');
export const useUser = createBridgeHook<User>('user', defaultUser);

Error Handling

Context Bridge provides helpful error messages in development:

Serialization Errors

If you try to pass non-serializable data:

// This will throw in development
<UserProvider data={{ onClick: () => {} }}>
  {children}
</UserProvider>

// Error: ContextBridge Warning [user]: Data passed to Provider is 'function'.
// It will be lost during serialization. Data must be JSON-serializable.

Parse Errors

If data cannot be parsed on the client:

// Console: ContextBridge Error: Unable to parse data from server.

Comparison with Other Solutions

vs React Context

Context Bridge:

  • Works with Server Components
  • No runtime Context Provider overhead
  • Data serialized once during SSR
  • Cannot be updated on client (by design)

React Context:

  • Only works in Client Components
  • Requires Provider wrapper
  • State can be updated
  • Not compatible with Server Components

vs Tanstack Query / SWR

Context Bridge:

  • Zero runtime overhead
  • Perfect for SSR data that doesn't change
  • No cache management needed
  • Simpler API

Tanstack Query / SWR:

  • Client-side caching and revalidation
  • Mutation support
  • Loading/error states
  • Better for dynamic data

Use Cases

Context Bridge is perfect for:

  • Passing user authentication data from server to client
  • Sharing server-fetched configuration across components
  • Providing initial data for client-side features
  • Avoiding prop drilling in Server Component trees
  • Sharing theme/locale data from server to client

Context Bridge is NOT suitable for:

  • Data that needs to be updated on the client
  • Real-time data that requires revalidation
  • Complex state management with actions/reducers
  • Data that contains functions or non-serializable objects

Troubleshooting

Hook returns default value

Problem: Client component always receives the default value.

Solutions:

  1. Ensure BridgeSerializer is added to root layout
  2. Check that Provider bridgeId matches hook bridgeId
  3. Verify Provider wraps the component using the hook
  4. Check browser DevTools for __CONTEXT_BRIDGE_STORE__ script tag

Serialization error

Problem: Error during build or runtime about serialization.

Solutions:

  1. Ensure data is JSON-serializable (no functions, symbols, circular refs)
  2. Convert Dates to strings: createdAt: date.toISOString()
  3. Remove or transform non-serializable properties

TypeScript errors

Problem: Type errors when using Provider or hook.

Solutions:

  1. Ensure same type T is used for both Provider and hook
  2. Check that data prop matches the defined type
  3. Verify TypeScript version is 5.0 or higher

Examples

Next.js App Router Example

Complete example with authentication:

// lib/auth-bridge.ts
import { createBridgeProvider } from '@tnnquang/context-bridge/server';
import { createBridgeHook } from '@tnnquang/context-bridge/client';

type AuthUser = {
  id: string;
  name: string;
  email: string;
  avatar?: string;
} | null;

export const AuthProvider = createBridgeProvider<AuthUser>('auth');
export const useAuth = createBridgeHook<AuthUser>('auth', null);

// app/layout.tsx
import { BridgeSerializer } from '@tnnquang/context-bridge/server';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <BridgeSerializer />
      </body>
    </html>
  );
}

// app/page.tsx (Server Component)
import { AuthProvider } from '@/lib/auth-bridge';
import { getServerSession } from '@/lib/auth';
import { Header } from '@/components/header';

export default async function Page() {
  const user = await getServerSession();

  return (
    <AuthProvider data={user}>
      <Header />
      <main>
        {/* Your content */}
      </main>
    </AuthProvider>
  );
}

// components/header.tsx (Client Component)
'use client';

import { useAuth } from '@/lib/auth-bridge';

export function Header() {
  const user = useAuth();

  return (
    <header>
      {user ? (
        <div>
          <img src={user.avatar} alt={user.name} />
          <span>Welcome, {user.name}</span>
        </div>
      ) : (
        <a href="/login">Sign In</a>
      )}
    </header>
  );
}

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Repository

https://github.com/tnnquang/context-bridge

Issues

https://github.com/tnnquang/context-bridge/issues