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 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-static-render

v3.0.0

Published

A CLI tool for rendering React components to static HTML files

Readme

react-static-render

React components to static HTML with template engine support (HTML, PHP, Liquid).

Installation

npm install -D react-static-render

Usage

CLI Usage

# Initialize config
react-static-render init

# Render all entry points
react-static-render

# Render specific files
react-static-render file1.tsx file2.tsx

# Watch mode with live reload
react-static-render --watch --live-reload

# List discovered entry points
react-static-render list

Programmatic API

You can also use react-static-render programmatically in your Node.js scripts:

import {
  renderFile,
  findEntryPoints,
  loadConfig,
  FileWatcher,
  LiveReloadServer
} from 'react-static-render'

// Load configuration
const configResult = await loadConfig()
if (!configResult.success) {
  console.error('Config error:', configResult.error)
  process.exit(1)
}
const config = configResult.data

// Find all entry points
const entryPoints = await findEntryPoints(config)

// Render a specific file
const result = await renderFile('pages/about.tsx', config)
if (result.success) {
  console.log('Rendered to:', result.outputPath)
} else {
  console.error('Error:', result.error)
}

// Start file watcher
const watcher = new FileWatcher(config, async (changedFiles) => {
  console.log('Files changed:', changedFiles)
  // Re-render changed files
  for (const file of changedFiles) {
    await renderFile(file, config)
  }
})
await watcher.start()

// Start live reload server
const liveReload = new LiveReloadServer(config)
liveReload.start()

Configuration

Configuration should be placed in react-static-render.config.json

Configuration Options

Core Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | entryPointDir | string | - | Required. Directory containing your React entry point files. The tool will search for files in this directory based on the configured file extensions. Path is relative to your project root | | outputDir | string | - | Required. Directory where rendered HTML files will be written. The directory structure from entry points will be preserved. For example, src/entry-points/pages/about.tsx becomes dist/pages/about.html | | templateDir | string | - | Required. Directory containing template files. Templates matching entry point names will be used for merging. Template files should have the same name as your entry point file (e.g., about.php for about.tsx) | | defaultTemplate | string | - | Optional. Default template file path (relative to templateDir) to use when a specific template is not found. The entry point's directory structure and filename are preserved in the output |

Rendering Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | templateEngine | "html" \| "php" \| "liquid" | "html" | Specifies which template engine to use for rendering templates. Choose "html" for plain HTML templates, "php" for PHP templates, or "liquid" for Liquid templates | | templateExtension | string | ".html" | Default file extension for template files when searching for matching templates. This is used when looking for template files that correspond to your entry points | | mountInfoExport | string | "default" | The export name to look for in entry files. Entry files should export an object with node and rootElementId properties. Use named exports by specifying the export name here | | fileExtensions | string[] | ["js", "jsx", "ts", "tsx"] | File extensions to consider as React entry points when searching the entry points directory. Add custom extensions if using non-standard file types |

Watch Mode Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | patterns | string[] | - | Additional glob patterns for files to watch beyond the default source directories. Useful for watching template files, shared components, or other dependencies. Example: ["src/**/*.tsx", "templates/**/*.php"] | | websocketPort | number | 3001 | Port number for the WebSocket server used by live reload. Must be between 1024 and 65535. Change this if port 3001 is already in use by another service |

Build Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | prettierConfig | object \| false | - | Prettier configuration object for formatting the generated HTML output. Set to false to disable formatting entirely. When enabled, uses Prettier's HTML parser. Supports all standard Prettier options like printWidth, tabWidth, useTabs, etc. |

Advanced Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxConcurrentRenders | number \| "auto" | "auto" | Maximum number of files to render in parallel. Use "auto" to automatically use the number of CPU cores, or specify a number to override. Decrease if experiencing memory issues or system instability | | verbose | boolean | false | Enable detailed logging output for debugging. Shows file processing steps, template matching logic, rendering progress, and error details. Useful for troubleshooting configuration issues |

Example Configurations

Minimal Configuration

{
  "entryPointDir": "src/entry-points",
  "outputDir": "dist",
  "templateDir": "templates"
}

Full Configuration with All Options

{
  "entryPointDir": "src/entry-points",
  "outputDir": "dist",
  "templateDir": "src/templates",
  "defaultTemplate": "default.html",
  "templateEngine": "html",
  "templateExtension": ".html",
  "mountInfoExport": "default",
  "fileExtensions": ["js", "jsx", "ts", "tsx"],
  "patterns": ["src/**/*.tsx", "src/**/*.jsx", "templates/**/*.php"],
  "websocketPort": 3001,
  "prettierConfig": {
    "printWidth": 120,
    "tabWidth": 2,
    "useTabs": false,
    "semi": true,
    "singleQuote": true
  },
  "maxConcurrentRenders": "auto",
  "verbose": false
}

Configuration for Liquid Templates

{
  "entryPointDir": "src/entry-points",
  "outputDir": "dist",
  "templateDir": "templates",
  "templateEngine": "liquid",
  "templateExtension": ".liquid",
  "patterns": ["templates/**/*.liquid", "src/**/*.tsx"]
}

Performance-Optimized Configuration

{
  "entryPointDir": "src/entry-points",
  "outputDir": "dist",
  "templateDir": "templates",
  "maxConcurrentRenders": 8,
  "prettierConfig": false,
  "verbose": false
}

Configuration Details

File Discovery and Processing

The tool discovers entry points by:

  1. Scanning entryPointDir directory recursively
  2. Finding files matching fileExtensions (default: js, jsx, ts, tsx)
  3. Loading files that export the required mount info structure
  4. Matching template files in templateDir with same base name

Template Engine Selection

The templateEngine option controls how templates are processed:

  • "html" - Uses plain HTML template processing (default)
  • "php" - Uses PHP template processing
  • "liquid" - Uses Liquid template processing

The engine you choose will be used for all template files regardless of their file extension.

Watch Mode Patterns

The watch mode monitors:

  • All files in entryPointDir directory (automatic)
  • Additional paths specified in patterns array
  • Template files if templateDir is configured

Example patterns:

{
  "patterns": [
    "src/components/**/*.tsx",    // Watch all TSX files in components
    "src/styles/**/*.css",        // Watch CSS files
    "templates/**/*.php",         // Watch PHP template files
    "!**/*.test.tsx"             // Exclude test files
  ]
}

Prettier Configuration

The prettierConfig option accepts:

  • false - Disable formatting entirely
  • Prettier options object - Configure HTML formatting

Common Prettier options:

{
  "prettierConfig": {
    "printWidth": 120,
    "tabWidth": 2,
    "useTabs": false,
    "htmlWhitespaceSensitivity": "css",
    "bracketSameLine": false
  }
}

Performance Tuning

  • maxConcurrentRenders: Defaults to "auto" (CPU core count)
    • Use "auto" for automatic CPU core detection (recommended)
    • Override with a specific number if needed (e.g., 2, 4, 8)
    • Reduce for memory-constrained environments
  • prettierConfig: false: Disable formatting for faster builds
  • verbose: true: Enable only when debugging issues

Entry Point Format

// src/entry-points/example.tsx
export default {
  node: <div>Hello World</div>,
  rootElementId: 'root'
}

CLI Options

| Option | Short | Description | Default | |--------|-------|-------------|---------| | --config | -c | Configuration file path | Auto-discover | | --watch | -w | Enable watch mode | false | | --live-reload | -l | Enable live reload | false | | --port | -p | WebSocket port | 8099 | | --output | -o | Output directory | From config | | --verbose | -v | Verbose logging | false |

Template Components

PHP Components

import {
  // Control Flow
  PhpIf, PhpElseIf, PhpElse,
  PhpSwitch, PhpCase, PhpDefault,
  
  // Loops
  PhpFor, PhpWhile, PhpForeach,
  
  // Output
  PhpEcho, PhpVar,
  
  // Includes
  PhpInclude, PhpRequire,
  
  // Comments & Raw PHP
  PhpComment, PhpLineComment, PhpRaw,
  
  // Template Literals
  php, echo
} from 'react-static-render/components/php'

// Example usage
<PhpIf condition="$users">
  <PhpForeach array="$users" variable="$user">
    <div>
      <PhpEcho value="$user['name']" escape={true} />
    </div>
  </PhpForeach>
  <PhpElseIf condition="$pending" />
  <p>Users pending...</p>
  <PhpElse />
  <p>No users found</p>
</PhpIf>

// Template literal functions
<div>
  {php`echo $title;`}
  {echo('$product->getName()', { escape: true })}
</div>

Liquid Components

import {
  // Control Flow
  LiquidIf, LiquidUnless, LiquidCase,
  
  // Loops
  LiquidFor, LiquidTablerow,
  
  // Output & Template
  LiquidObject, LiquidTag,
  
  // Comments & Raw
  LiquidComment, LiquidRaw
} from 'react-static-render/components/liquid'

// Example usage
<LiquidIf condition="product.available">
  <LiquidFor item="variant" collection="product.variants">
    <div>
      <LiquidObject>variant.title</LiquidObject>
    </div>
  </LiquidFor>
</LiquidIf>

// Case statement
<LiquidCase value="product.type">
  <LiquidCase.When condition="shirt">
    <p>This is a shirt</p>
  </LiquidCase.When>
  <LiquidCase.When condition="pants">
    <p>This is pants</p>
  </LiquidCase.When>
  <LiquidCase.Else>
    <p>Unknown product type</p>
  </LiquidCase.Else>
</LiquidCase>

Script Component

import { Script } from 'react-static-render/components/script'

// Execute a function in the browser
<Script 
  fn={() => {
    console.log('Hello from browser!')
  }}
/>

// Execute a function with arguments
<Script 
  fn={(name: string, age: number) => {
    console.log(`Hello ${name}, you are ${age} years old`)
  }}
  args={['John', 25]}
/>

The Script component allows you to inject and execute JavaScript functions in the browser. The function is serialized and executed after the DOM content is loaded.

Template Insertion

All template engines (HTML, PHP, and Liquid) require <div id="rootElementId"></div> to exist in the template file. The engines will replace the content within this div with the rendered React component. If the div is not found, the build will fail with an error.

Live Reload Client Script

When using --live-reload mode, add this script to your template files:

<script>
(() => {
  if (typeof window === 'undefined') return
  function connect() {
    const ws = new WebSocket('ws://localhost:8099')
    ws.addEventListener('message', e => {
      try { if (JSON.parse(e.data).type === 'reload') location.reload() }
      catch (err) { console.warn('Live reload error:', err) }
    })
    ws.addEventListener('close', () => setTimeout(connect, 1000))
    ws.addEventListener('error', () => setTimeout(connect, 1000))
  }
  connect()
})()
</script>

Replace 8099 with your configured websocketPort if different.

License

SIC