Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@ecopages/react

Package Overview
Dependencies
Maintainers
1
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ecopages/react

React integration for Ecopages

Source
npmnpm
Version
0.2.0-beta.2
Version published
Maintainers
1
Created
Source

@ecopages/react

First-class integration for React 19 in Ecopages. This plugin enables React SSR and client hydration, allowing you to build component-level React islands or full React Single Page Applications (SPAs).

Installation

bun add @ecopages/react react react-dom
bun add -d @types/react @types/react-dom

Usage

Configure the plugin in your eco.config.ts:

import { ConfigBuilder } from '@ecopages/core/config-builder';
import { reactPlugin } from '@ecopages/react';

const config = await new ConfigBuilder()
	.setBaseUrl(import.meta.env.ECOPAGES_BASE_URL)
	.setIntegrations([reactPlugin()])
	.build();

export default config;

Component-Level Islands

For component-level islands, Ecopages React uses this contract:

  • SSR output preserves the authored DOM structure (no unnecessary wrapper elements).
  • A stable data-eco-component-id attribute is attached to the component SSR root.
  • The island runtime replaces the SSR host with a dedicated client-owned container and mounts it with createRoot(). Full-page hydration paths use hydrateRoot().

[!TIP] Full React SPA Routing: If you are building full React pages and want client-side navigation (SPA), use @ecopages/react-router and pass it to the react plugin: reactPlugin({ router: ecoRouter() }).

MDX Support

The React plugin includes built-in MDX support. When enabled, you can write .mdx pages alongside .tsx pages with unified client-side routing, hydration, and HMR.

import { ConfigBuilder } from '@ecopages/core/config-builder';
import { reactPlugin } from '@ecopages/react';

const config = await new ConfigBuilder()
	.setIntegrations([
		reactPlugin({
			mdx: {
				enabled: true,
				compilerOptions: {
					// Optional: remark/rehype plugins
				},
			},
		}),
	])
	.build();

export default config;

Mixed Rendering

The React integration can participate in mixed-renderer apps in three ways:

  • React can own the page or view directly.
  • React can render nested foreign subtrees inside pages owned by another integration.
  • React can render through non-React page, layout, or document shells when those shell components return strings.

When a non-React render pass reaches a React-owned foreign child, Ecopages hands that foreign subtree back to the React renderer. When React renders through a non-React shell, that shell must serialize to HTML so React can insert the result into the final response without escaping it.

Important:

  • Components that may render foreign children must declare those children in config.dependencies.components.
  • Ecopages validates mixed-renderer ownership from declared dependencies during render preparation. It does not infer every foreign subtree from rendered HTML alone.
  • React still keeps its own child transport and hydration rules for React-owned subtrees.

Server and Client Graph Contract

The React integration supports Node.js modules and server-only code only on the server execution graph.

  • Server rendering can safely import node:* modules, database clients, filesystem utilities, etc.
  • Client-hydrated React code must resolve to browser-safe modules only.
  • If a server-only import crosses the boundary and becomes reachable by client code, the client build will intentionally fail.

Keep server helpers close, but separate them physically or logically so they do not leak into the client bundle.

Client Graph Boundary Architecture

This section explains the internal contract used to keep the browser bundle minimal while preventing server-only code and request-only configuration from leaking into client output.

Goal

The React integration has two jobs that must hold at the same time:

  • Produce a browser-safe bundle for hydrated pages and islands.
  • Preserve enough page code for hydration to reconstruct the same React tree the server rendered.

That means the client bundle must keep client-safe render logic, but it must drop server-only imports and server-only eco.page(...) options such as middleware and build-time metadata.

Mental Model

Think about each React page as two related graphs:

  • Server graph: everything needed to render the page on the server. This graph may include middleware, request locals, database access, filesystem access, and other server-only modules.
  • Client graph: the smallest browser-safe subset needed to hydrate the rendered output in the browser.

The React integration builds the client graph conservatively. If a server-only module becomes reachable from the hydrated render path, the build should fail rather than silently shipping unsafe code.

What Stays and What Goes

The client bundle keeps:

  • The page component render path.
  • Client-safe component dependencies reachable from render.
  • Layout wiring needed for hydration.
  • Router runtime state needed by @ecopages/react-router when SPA mode is enabled.

The client bundle removes or excludes:

  • Server-only imports that are not reachable from the hydrated render path.
  • Server-only eco.page(...) options such as cache, middleware, metadata, staticProps, and staticPaths.
  • Request-time configuration that has no meaning in the browser.

Important:

  • render must stay in the client bundle, because hydration needs it to reconstruct the page tree.
  • requires does not stay in the browser page config. It is used on the server to decide which locals keys may be serialized into the hydration payload.

AST Pipeline Order

The browser-bound transform in src/utils/client-graph-boundary-plugin.ts follows this order:

  • Parse the module and build a reachability view of the client render graph.
  • Remove imports that are not allowed or not reachable from the client graph.
  • Reparse the transformed source.
  • Strip server-only eco.page(...) object properties from the reparsed AST.
  • Return the rewritten source to the bundle step.

The reparse step is important. Once import edits change source offsets, the original AST locations are stale. Reusing them for later edits can corrupt the output or remove the wrong code.

Why eco.page(...) Options Are Stripped

Import pruning alone is not enough.

Consider a page like this:

import { authMiddleware } from './auth.server';

export default eco.page({
	cache: 'dynamic',
	middleware: [authMiddleware],
	requires: ['session'] as const,
	render: () => <div>Dashboard</div>,
});

If the client transform removes the auth.server import but leaves middleware: [authMiddleware] in place, the browser bundle still contains a dangling identifier. That breaks production hydration even though the import was removed correctly.

The fix is to strip server-only eco.page(...) options after import pruning, while keeping render intact.

Hydration Contract for locals

The browser must not receive arbitrary request-scoped data.

The React renderer in src/react-renderer.ts serializes only the top-level locals keys explicitly declared by Page.requires. If a page does not declare requires, no locals are serialized for hydration.

Example:

export default eco.page({
	requires: ['session'] as const,
	render: ({ locals }) => <Dashboard user={locals?.session?.user} />,
});

In this case, the hydration payload may include locals.session, but it will exclude unrelated request-only keys.

Important:

  • This filtering is currently top-level only.
  • If locals.session itself contains sensitive nested fields, those fields will still be serialized.
  • Middleware should therefore expose a client-safe shape for any key declared in requires.

Layout Hydration Invariant

Hydration must rebuild the same tree the server rendered.

That applies to both:

If the page render receives locals on the server and the layout also depends on those values, the client must pass the same serialized locals into the layout during hydration. Otherwise React will detect a mismatch.

Tests That Guard This Contract

The main regression coverage lives in:

If you change the AST transform or hydration flow, update the corresponding tests in the same change.

Keywords

ecopages

FAQs

Package last updated on 10 Jun 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts