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

frame-master-plugin-apply-react

v3.0.0

Published

A Frame-Master plugin that adds React client-side hydration and interactivity to static HTML files, enabling dynamic single-page applications with SSG (Static Site Generation) and CDN deployment.

Readme

Apply React Plugin

A Frame-Master plugin that adds React client-side hydration and interactivity to static HTML files, enabling dynamic single-page applications with SSG (Static Site Generation) and CDN deployment.

Features

  • Client-Side Hydration - Transforms static HTML into interactive React applications
  • 🔄 Client-Side Navigation - Seamless SPA routing without full page reloads
  • 🔥 Hot Module Replacement - Live reload during development for instant feedback
  • 📦 SSG + React - Combines static site generation with dynamic React functionality
  • 🌐 CDN Ready - Optimized builds suitable for CDN distribution
  • 🎯 File-Based Routing - Automatic route generation from file structure
  • 🛡️ Server-Only Protection - Prevents server-side code from bundling client-side

Installation

bun add frame-master-plugin-apply-react

Requires Frame-Master 4.x (frame-master@^4.0.0-0).

Quick Start

1. Configure the Plugin

Use this plugin together with frame-master-plugin-react-to-html for full SSG + React functionality.

// frame-master.config.ts
import type { FrameMasterConfig } from "frame-master/server/types";
import ReactToHtml from "frame-master-plugin-react-to-html";
import ApplyReact from "frame-master-plugin-apply-react/plugin";

const config: FrameMasterConfig = {
  HTTPServer: { port: 3000 },
  plugins: [
    ReactToHtml({
      outDir: ".frame-master/build",
      srcDir: "src/pages",
      shellPath: "src/shell.tsx",
    }),
    ApplyReact({
      style: "nextjs",
      route: "src/pages",
      enableHMR: true,
      hydration: "hydrate",
    }),
  ],
};

export default config;

2. Create a Client Shell (Optional)

// src/client-shell.tsx
import { RouterHost } from "frame-master-plugin-apply-react/router";

export default function ClientShell({ children }: { children: JSX.Element }) {
  return <RouterHost>{children}</RouterHost>;
}

3. Build Interactive Pages

// src/pages/index.tsx
import { useState } from "react";

export default function HomePage() {
  const [count, setCount] = useState(0);

  return (
    <section>
      <h1>Welcome to My Interactive Site</h1>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <a href="/about">Learn More</a>
    </section>
  );
}

4. Add Layouts

// src/pages/layout.tsx
export default function MainLayout({ children }: { children: JSX.Element }) {
  return (
    <>
      <header>
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
          <a href="/contact">Contact</a>
        </nav>
      </header>
      <main>{children}</main>
      <footer>
        <p>&copy; 2024 My Company</p>
      </footer>
    </>
  );
}

Configuration Options

| Option | Type | Default | Description | | ------------------------- | ----------- | ----------------------- | ------------------------------------------------------------------------ | | style | "nextjs" | - | Routing convention style (currently supports Next.js style) | | route | string | - | Base path to your routes directory | | clientShellPath | string? | - | Optional path to a custom client-side shell component | | enableHMR | boolean | true | Enable Hot Module Replacement for development | | enableFastRefresh | boolean? | enableHMR | Preserve compatible React state and shared context identity during HMR | | HMROptions.websocket | "ws" \| "wss" \| "auto"? | "auto" | Client HMR socket scheme; auto uses wss on HTTPS pages (tunnels) | | watchDirectories | string[]? | ['.', 'node_modules'] | Directories watched for HMR file changes (project-root relative) | | watchDirectoriesExclude | string[]? | - | Directories excluded from HMR watching; applied after watchDirectories | | hydration | "hydrate" | "hydrate" | Hydration method to use on the client |

How It Works

Static Generation + Client Hydration

  1. Build Time: react-to-html plugin generates static HTML files from your React components
  2. Client Load: Static HTML is served instantly from CDN for fast initial load
  3. Hydration: apply-react plugin attaches React event listeners to the static markup
  4. Navigation: Client-side routing takes over for seamless SPA-like navigation

Development Workflow

During development, the HMR system:

  • Watches for file changes in your pages directory
  • Automatically updates the client without full page reload
  • Uses React Fast Refresh to retain component and provider state when React marks the update boundary compatible
  • Preserves the identity of top-level exported contexts created with createContext, including aliased and namespace React imports, so layouts and pages continue to share the same provider after a route rebuild
  • Provides instant feedback via WebSocket connection

Fast Refresh instrumentation is development-only. Context identity is stabilized for top-level exported contexts such as export const ThemeContext = createContext(...); function-local or dynamically-created contexts retain normal React behavior. When a hook signature or refresh boundary is incompatible, React remounts the affected boundary rather than retaining stale state.

Client-Side Router

The plugin provides a RouterHost component that handles:

  • Link Interception: Automatically intercepts <a> tag clicks for client-side navigation
  • History Management: Integrates with browser history API (back/forward buttons)
  • Layout Wrapping: Automatically wraps pages with their corresponding layouts
  • HMR Integration: Updates routes dynamically during development
  • Error Fallback: Catches errors thrown inside page components and maps them to fallback pages
import { RouterHost } from "frame-master-plugin-apply-react/router";

export default function ClientShell({ children }: { children: JSX.Element }) {
  return <RouterHost>{children}</RouterHost>;
}

Fallback Pages & Error Handling

RouterHost wraps every rendered page inside an ErrorWrapper — a React error boundary. When a page component throws, the error is passed through a resolver chain that maps it to a fallback page component.

Built-in: NotFoundError

Throw ThrowNotFound() (or new NotFoundError()) anywhere inside a page to trigger the nearest co-located 404.tsx file, exactly like a route miss.

// src/pages/users/[userId].tsx
import { ThrowNotFound } from "frame-master-plugin-apply-react/utils";

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

  if (!user) ThrowNotFound(); // renders src/pages/users/404.tsx

  return <div>{user.name}</div>;
}
// src/pages/users/404.tsx
export default function UserNotFound() {
  return <h1>User not found</h1>;
}

Custom Error Resolvers

Pass an errorResolvers array to RouterHost to handle your own error types. Each resolver is an async function (error, pathname) => (() => JSX.Element) | null. Return a component to handle the error, or null to fall through to the next resolver.

import {
  RouterHost,
  defaultErrorResolvers,
  type ErrorFallbackResolver,
} from "frame-master-plugin-apply-react/router";

class UnauthorizedError extends Error {}

const myResolvers: ErrorFallbackResolver[] = [
  async (error, pathname) => {
    if (error instanceof UnauthorizedError) {
      return () => <LoginPage />;
    }
    return null; // fall through
  },
  ...defaultErrorResolvers, // keep built-in NotFoundError handling
];

export default function ClientShell({ children }: { children: JSX.Element }) {
  return <RouterHost errorResolvers={myResolvers}>{children}</RouterHost>;
}

If no resolver matches, RouterHost renders a recoverable built-in fallback. In development it includes the message, component stack, current pathname, and retry/reload/copy actions. Production uses a generic fallback without error details. The boundary resets on navigation and after a successful Fast Refresh update.

Custom Fallbacks And Reporting

Use errorFallback to replace the built-in fallback, and onError to send error details to your logging service. Typed errorResolvers always take precedence over errorFallback.

import {
  RouterHost,
  type RouterErrorFallbackProps,
} from "frame-master-plugin-apply-react/router";

function AppError({ error, reset }: RouterErrorFallbackProps) {
  return (
    <main>
      <h1>We could not load this page</h1>
      <p>{error.message}</p>
      <button type="button" onClick={reset}>Try again</button>
    </main>
  );
}

export default function ClientShell({ children }: { children: JSX.Element }) {
  return (
    <RouterHost
      errorFallback={AppError}
      onError={(error, { pathname, componentStack }) => {
        reportRouteError({ error, pathname, componentStack });
      }}
    >
      {children}
    </RouterHost>
  );
}

ErrorFallbackResolver type

type ErrorFallbackResolver = (
  error: Error,
  pathname: string,
) => Promise<(() => JSX.Element) | null>;
type RouterErrorFallbackProps = {
  error: Error;
  componentStack: string | null;
  pathname: string;
  reset: () => void;
};

Server-Only Modules

The plugin automatically protects server-only code from being bundled in the client build. Original exports are replaced with stubs that throw Cannot use <export> on a client build (server-only) if the module is imported client-side.

Best Practices

1. Use React Hooks

Unlike the static react-to-html plugin, apply-react fully supports React hooks and state management:

import { useState, useEffect } from "react";

export default function InteractivePage() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch("/api/data")
      .then((res) => res.json())
      .then(setData);
  }, []);

  return <div>{/* Interactive content */}</div>;
}

2. Optimize for CDN

  • Keep your build output small by code-splitting
  • Use dynamic imports for large components
  • Leverage the static HTML for SEO and initial load performance

3. Development vs Production

  • Development: Enable HMR for fast iteration
  • Production: Disable HMR and optimize for bundle size

Deployment

The generated build can be deployed to any CDN:

  1. Run your build process
  2. Upload the .frame-master/build directory to your CDN
  3. Configure your CDN to serve index.html for SPA routing

Comparison with react-to-html

| Feature | react-to-html | apply-react | | ---------------------- | ------------- | ------------ | | Static HTML Generation | ✅ | ❌ | | React Hooks | ❌ | ✅ | | Client-Side State | ❌ | ✅ | | Client-Side Navigation | ❌ | ✅ | | Event Handlers | ❌ | ✅ | | HMR | ❌ | ✅ | | CDN Ready | ✅ | ✅ | | SEO Friendly | ✅ | ✅ (via SSG) |

Recommendation: Use both plugins together for the best of both worlds - fast initial load with static HTML and full React interactivity after hydration.

License

MIT