🚀 Getting Started with Coherent.js

Welcome to Coherent.js! This guide will get you up and running with server-side rendering, state management, and client-side hydration in minutes.

What is Coherent.js?

Coherent.js is a modern server-side rendering framework that uses pure JavaScript objects to define components. It provides:

  • Universal Components: Same components work on server and client
  • Pure JavaScript: No JSX, no build step required
  • Type Safety: Full TypeScript support out of the box
  • State Management: Built-in reactive state with withState
  • Client-Side Hydration: Make server-rendered components interactive
  • Performance Optimized: Built-in caching and performance monitoring

Quick Start

Installation

# Using npm
npm install coherent-js

# Using pnpm (recommended)
pnpm add coherent-js

# Using yarn
yarn add coherent-js

Your First Component

// components/Greeting.js
export const Greeting = ({ name = 'World' }) => ({
  div: {
    className: 'greeting',
    children: [
      { h1: { text: `Hello, ${name}!` } },
      { p: { text: 'Welcome to Coherent.js' } }
    ]
  }
});

Server-Side Rendering

// server.js
import { renderToString } from 'coherent-js';
import { Greeting } from './components/Greeting.js';

const html = renderToString(Greeting({ name: 'Developer' }));
console.log(html);
// Output: <div class="greeting"><h1>Hello, Developer!</h1><p>Welcome to Coherent.js</p></div>

Adding Interactivity

Step 1: Create a Stateful Component

// components/Counter.js
import { withState } from 'coherent-js';

const CounterComponent = withState({ count: 0 })(({ state, stateUtils }) => {
  const { setState } = stateUtils;

  const increment = () => setState({ count: state.count + 1 });
  const decrement = () => setState({ count: state.count - 1 });
  const reset = () => setState({ count: 0 });

  return {
    div: {
      'data-coherent-component': 'counter', // Required for hydration
      className: 'counter-widget',
      children: [
        { h2: { text: 'Interactive Counter' } },
        { p: { text: `Count: ${state.count}`, className: 'count-display' } },
        {
          div: {
            className: 'counter-controls',
            children: [
              { button: { text: '−', onclick: decrement, className: 'btn' } },
              { button: { text: 'Reset', onclick: reset, className: 'btn' } },
              { button: { text: '+', onclick: increment, className: 'btn' } }
            ]
          }
        }
      ]
    }
  };
});

export const Counter = CounterComponent;

Step 2: Set Up Client-Side Hydration

// hydration.js
import { makeHydratable, autoHydrate } from 'coherent-js/client';
import { Counter } from './components/Counter.js';

// Make the component hydratable
const HydratableCounter = makeHydratable(Counter, {
  componentName: 'counter'
});

// Set up hydration when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
  autoHydrate({
    counter: HydratableCounter
  });
});

Step 3: Create a Complete Page

// pages/app.js
import { Counter } from './components/Counter.js';

export const appPage = {
  html: {
    children: [
      {
        head: {
          children: [
            { title: { text: 'My Coherent.js App' } },
            {
              style: {
                text: `
                  body { font-family: Arial, sans-serif; margin: 20px; }
                  .counter-widget { 
                    background: #f8f9fa; 
                    padding: 20px; 
                    border-radius: 8px; 
                    margin: 20px 0;
                    border: 1px solid #e9ecef;
                  }
                  .count-display { 
                    font-size: 1.5rem; 
                    font-weight: bold; 
                    text-align: center;
                    margin: 15px 0;
                  }
                  .counter-controls { 
                    display: flex; 
                    gap: 10px; 
                    justify-content: center; 
                  }
                  .btn { 
                    padding: 10px 20px; 
                    border: none; 
                    border-radius: 4px; 
                    cursor: pointer; 
                    background: #007bff; 
                    color: white; 
                  }
                  .btn:hover { background: #0056b3; }
                `
              }
            },
            { script: { src: './hydration.js', defer: true } }
          ]
        }
      },
      {
        body: {
          children: [
            { h1: { text: 'My Coherent.js App' } },
            { p: { text: 'This counter works on both server and client!' } },
            Counter()
          ]
        }
      }
    ]
  }
};

Step 4: Serve Your App

// server.js
import express from 'express';
import { renderToString } from 'coherent-js';
import { appPage } from './pages/app.js';

const app = express();

// Serve static files (hydration.js, etc.)
app.use(express.static('public'));

// Serve the main page
app.get('/', (req, res) => {
  const html = renderToString(appPage);
  res.send(html);
});

app.listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});

Project Structure

Here's a recommended project structure:

my-coherent-app/
├── components/
│   ├── Counter.js
│   ├── Greeting.js
│   └── Layout.js
├── pages/
│   ├── home.js
│   └── about.js
├── public/
│   ├── hydration.js
│   └── styles.css
├── server.js
└── package.json

Core Concepts

1. Component Definition

Components are pure functions that return JavaScript objects:

export const MyComponent = ({ title, children }) => ({
  div: {
    className: 'my-component',
    children: [
      { h2: { text: title } },
      ...children
    ]
  }
});

2. State Management

Use withState for reactive components:

import { withState } from 'coherent-js';

const StatefulComponent = withState({ 
  value: '', 
  submitted: false 
})(({ state, stateUtils }) => {
  const { setState } = stateUtils;
  
  // Component logic here
  return { /* component object */ };
});

3. Event Handlers

Event handlers are functions that become data-action attributes during SSR:

{
  button: {
    text: 'Click me',
    onclick: () => setState({ clicked: true })
  }
}

// Renders as: <button data-action="..." data-event="click">Click me</button>

4. Client-Side Hydration

Make server-rendered components interactive:

import { makeHydratable, autoHydrate } from 'coherent-js/client';

// 1. Make component hydratable
const HydratableComponent = makeHydratable(MyComponent, {
  componentName: 'my-component'
});

// 2. Set up auto-hydration
autoHydrate({
  'my-component': HydratableComponent
});

5. Component Identification

Always add data-coherent-component to interactive components:

{
  div: {
    'data-coherent-component': 'my-component', // Required!
    children: [...]
  }
}

Common Patterns

Form Handling

const ContactForm = withState({
  name: '',
  email: '',
  message: '',
  submitted: false
})(({ state, stateUtils }) => {
  const { setState } = stateUtils;

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Submitting:', state);
    setState({ submitted: true });
  };

  const updateField = (field) => (event) => {
    setState({ [field]: event.target.value });
  };

  return {
    form: {
      'data-coherent-component': 'contact-form',
      onsubmit: handleSubmit,
      children: [
        {
          input: {
            type: 'text',
            placeholder: 'Name',
            value: state.name,
            oninput: updateField('name')
          }
        },
        {
          input: {
            type: 'email',
            placeholder: 'Email',
            value: state.email,
            oninput: updateField('email')
          }
        },
        {
          textarea: {
            placeholder: 'Message',
            value: state.message,
            oninput: updateField('message')
          }
        },
        {
          button: {
            type: 'submit',
            text: state.submitted ? 'Sent!' : 'Send'
          }
        }
      ]
    }
  };
});

List Rendering

const TodoList = ({ todos = [] }) => ({
  ul: {
    className: 'todo-list',
    children: todos.map(todo => ({
      li: {
        key: todo.id,
        className: todo.completed ? 'completed' : 'pending',
        children: [
          { span: { text: todo.text } },
          {
            button: {
              text: '✓',
              onclick: () => toggleTodo(todo.id)
            }
          }
        ]
      }
    }))
  }
});

Conditional Rendering

import { when } from 'coherent-js';

const UserProfile = ({ user }) => ({
  div: {
    children: [
      when(user,
        { p: { text: `Welcome, ${user.name}!` } },
        { p: { text: 'Please log in' } }
      )
    ]
  }
});

Integration Examples

With Express.js

import express from 'express';
import { renderToString } from 'coherent-js';

const app = express();

app.get('*', (req, res) => {
  const component = getComponentForRoute(req.path);
  const html = renderToString(component);
  res.send(html);
});

With Next.js

// pages/index.js
import { renderToString } from 'coherent-js';
import { HomePage } from '../components/HomePage.js';

export default function Page(props) {
  const html = renderToString(HomePage(props));
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

Progressive Enhancement

Ensure your components work without JavaScript:

{
  form: {
    action: '/api/submit',      // Works without JS
    method: 'POST',
    onsubmit: enhancedSubmit,   // Enhanced with JS
    children: [
      // Form fields
    ]
  }
}

Best Practices

1. Component Structure

// ✅ Good - clear structure
export const UserCard = ({ user }) => ({
  div: {
    className: 'user-card',
    'data-coherent-component': 'user-card',
    children: [
      { h3: { text: user.name } },
      { p: { text: user.email } },
      { img: { src: user.avatar, alt: user.name } }
    ]
  }
});

// ❌ Bad - unclear structure  
export const UserCard = ({ user }) => ({
  div: {
    children: [user.name, user.email, user.avatar].map(item => ({ p: { text: item } }))
  }
});

2. State Management

// ✅ Good - immutable updates
setState({ todos: [...state.todos, newTodo] });

// ❌ Bad - direct mutation
state.todos.push(newTodo);
setState(state);

3. Event Handlers

// ✅ Good - proper event handling
const handleClick = (event) => {
  event.preventDefault();
  setState({ clicked: true });
};

// ❌ Bad - no event handling
const handleClick = () => {
  setState({ clicked: true }); // Form will submit!
};

4. Hydration Setup

// ✅ Good - proper timing
document.addEventListener('DOMContentLoaded', () => {
  setTimeout(() => {
    autoHydrate(componentRegistry);
  }, 100);
});

// ❌ Bad - too early
autoHydrate(componentRegistry); // DOM might not be ready

5. Error Handling

// ✅ Good - with error handling
try {
  autoHydrate(componentRegistry);
  console.log('Hydration successful');
} catch (error) {
  console.error('Hydration failed:', error);
  // Provide fallback behavior
}

Debugging

Enable Debug Mode

// For state components
const DebugComponent = withState(initialState, {
  debug: true // Logs all state changes
});

// For hydration
window.COHERENT_DEBUG = true;

Common Debug Commands

// Check if components are found
document.querySelectorAll('[data-coherent-component]');

// Check if handlers are available
console.log(typeof window.myHandler);

// Verify button connections
document.querySelectorAll('button[data-action]');

Next Steps

  1. Explore Examples: Check out the examples/ directory for more patterns
  2. Read the API Reference: Full documentation of all functions and options
  3. Migration Guide: Moving from React, Vue, or other frameworks
  4. Performance Guide: Optimization strategies and best practices
  5. Advanced Features: Streaming, caching, and performance monitoring

Getting Help

  • Documentation: Complete API reference and guides
  • Examples: Real-world usage patterns in examples/
  • Issues: Report bugs and request features on GitHub
  • Community: Join discussions and get help

You're now ready to build universal, performant web applications with Coherent.js! Start with simple components and gradually add state management and interactivity as needed.