Files
OpenNoodl/dev-docs/future-projects/SSR-SUPPORT.md
2025-12-17 09:30:30 +01:00

9.9 KiB

Future: Server-Side Rendering (SSR) Support

Status: Planning
Priority: Medium
Complexity: High
Prerequisites: React 19 migration, HTTP node implementation

Executive Summary

OpenNoodl has substantial existing SSR infrastructure that was developed but never shipped by the original Noodl team. This document outlines a path to completing and exposing SSR as a user-facing feature, giving users the choice between client-side rendering (CSR), server-side rendering (SSR), and static site generation (SSG).

Why SSR Matters

The Problem with Pure CSR

Currently, Noodl apps are entirely client-side rendered:

  1. SEO Limitations: Search engine crawlers see an empty <div id="root"></div> until JavaScript executes
  2. Social Sharing: Link previews on Twitter, Facebook, Slack, etc. show blank or generic content
  3. First Paint Performance: Users see a blank screen while the runtime loads and initializes
  4. Core Web Vitals: Poor Largest Contentful Paint (LCP) scores affect search rankings

What SSR Provides

Metric CSR SSR SSG
SEO Poor Excellent Excellent
Social Previews Broken Working Working
First Paint Slow Fast Fastest
Hosting Requirements Static Node.js Server Static
Dynamic Content Real-time Real-time Build-time
Build Complexity Low Medium Medium

Current State in Codebase

What Already Exists

The original Noodl team built significant SSR infrastructure:

SSR Server (packages/noodl-viewer-react/static/ssr/)

  • Express server with route handling
  • ReactDOMServer.renderToString() integration
  • Browser API polyfills (localStorage, fetch, XMLHttpRequest, requestAnimationFrame)
  • Result caching via node-cache
  • Graceful fallback to CSR on errors

SEO API (Noodl.SEO)

  • setTitle(value) - Update document title
  • setMeta(key, value) - Set meta tags
  • getMeta(key) / clearMeta() - Manage meta tags
  • Designed specifically for SSR (no direct window access)

Deploy Infrastructure

  • runtimeType parameter supports 'ssr' value
  • Separate deploy index for SSR files (ssr/index.json)
  • Commented-out UI code showing intended deployment flow

Build Scripts

  • getPages() API returns all routes with metadata
  • createIndexPage() generates HTML with custom meta tags
  • expandPaths() for dynamic route expansion
  • Sitemap generation support

What's Incomplete

  • SEO meta injection not implemented (// TODO: Inject Noodl.SEO.meta)
  • Page router issues (// TODO: Maybe fix page router)
  • No UI for selecting SSR deployment
  • No documentation or user guidance
  • Untested with modern component library
  • No hydration verification

Proposed User Experience

Option 1: Project-Level Setting

Add rendering mode selection in Project Settings:

Rendering Mode:
  ○ Client-Side (CSR) - Default, works with any static host
  ○ Server-Side (SSR) - Better SEO, requires Node.js hosting  
  ○ Static Generation (SSG) - Best performance, pre-renders at build time

Pros: Simple mental model, single source of truth
Cons: All-or-nothing, can't mix approaches

Option 2: Deploy-Time Selection

Add rendering mode choice in Deploy popup:

Deploy Target:
  [Static Files (CSR)]  [Node.js Server (SSR)]  [Pre-rendered (SSG)]

Pros: Flexible, same project can deploy differently
Cons: Could be confusing, settings disconnect

Add per-page rendering configuration in Page Router settings:

Page: /blog/{slug}
  Rendering: [SSR ▼]
  
Page: /dashboard  
  Rendering: [CSR ▼]
  
Page: /about
  Rendering: [SSG ▼]

Pros: Maximum flexibility, matches real-world needs
Cons: More complex, requires smarter build system

Phase 1: Start with Option 2 (Deploy-Time Selection) - simplest to implement
Phase 2: Add Option 1 (Project Setting) for default behavior
Phase 3: Consider Option 3 (Page-Level) based on user demand

Technical Implementation

Phase 1: Complete Existing SSR Infrastructure

1.1 Fix Page Router for SSR

  • Ensure globalThis.location properly simulates browser location
  • Handle query parameters and hash fragments
  • Support Page Router navigation events

1.2 Implement SEO Meta Injection

// In ssr/index.js buildPage()
const result = htmlData
  .replace('<div id="root"></div>', `<div id="root">${output1}</div>`)
  .replace('</head>', `${generateMetaTags(noodlRuntime.SEO.meta)}</head>`);

1.3 Polyfill Audit

  • Test all visual nodes in SSR context
  • Identify browser-only APIs that need polyfills
  • Create SSR compatibility matrix for nodes

Phase 2: Deploy UI Integration

2.1 Add SSR Option to Deploy Popup

// DeployToFolderTab.tsx
<Select
  options={[
    { value: 'csr', label: 'Client-Side Rendering (Static)' },
    { value: 'ssr', label: 'Server-Side Rendering (Node.js)' },
    { value: 'ssg', label: 'Static Site Generation' }
  ]}
  value={renderingMode}
  onChange={setRenderingMode}
  label="Rendering Mode"
/>

2.2 SSR Deploy Flow

if (renderingMode === 'ssr') {
  // Deploy SSR server files to root
  await compilation.deployToFolder(direntry, {
    environment,
    runtimeType: 'ssr'
  });
  // Deploy static assets to /public
  await compilation.deployToFolder(direntry + '/public', {
    environment,
    runtimeType: 'deploy'
  });
}

2.3 SSG Build Flow

if (renderingMode === 'ssg') {
  // Deploy static files
  await compilation.deployToFolder(direntry, { environment });
  
  // Pre-render each page
  const pages = await context.getPages({ expandPaths: ... });
  for (const page of pages) {
    const html = await prerenderPage(page.path);
    await writeFile(`${direntry}${page.path}/index.html`, html);
  }
}

Phase 3: Enhanced SEO Tools

3.1 SEO Node Create a visual node for setting page metadata:

┌─────────────────────────────┐
│ SEO Settings                │
├─────────────────────────────┤
│ ► Title          [string]   │
│ ► Description    [string]   │
│ ► Image URL      [string]   │
│ ► Keywords       [string]   │
│ ► Canonical URL  [string]   │
│ ► Robots         [string]   │
└─────────────────────────────┘

3.2 Open Graph Support Extend Noodl.SEO API:

Noodl.SEO.setOpenGraph({
  title: 'My Page',
  description: 'Page description',
  image: 'https://example.com/image.jpg',
  type: 'website'
});

Noodl.SEO.setTwitterCard({
  card: 'summary_large_image',
  site: '@mysite'
});

3.3 Structured Data

Noodl.SEO.setStructuredData({
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "My Article",
  "author": { "@type": "Person", "name": "Author" }
});

Phase 4: Hosting Integration

4.1 One-Click Deploy Targets

  • Vercel (native SSR support)
  • Netlify (serverless functions for SSR)
  • Railway / Render (Node.js hosting)
  • Docker container export

4.2 Deploy Configuration Generation

// Generate vercel.json
{
  "builds": [
    { "src": "server.js", "use": "@vercel/node" },
    { "src": "public/**", "use": "@vercel/static" }
  ],
  "routes": [
    { "src": "/public/(.*)", "dest": "/public/$1" },
    { "src": "/(.*)", "dest": "/server.js" }
  ]
}

Component SSR Compatibility

Compatibility Levels

Level A: Full SSR Support

  • Text, Group, Columns, Image (static src)
  • All layout nodes
  • Style properties

Level B: Hydration Required

  • Video, Animation
  • Interactive components
  • Event handlers

Level C: Client-Only

  • Camera, Geolocation
  • Local Storage operations
  • WebSocket connections

Handling Incompatible Components

// In component definition
{
  ssr: {
    supported: false,
    fallback: '<div class="placeholder">Loading video...</div>'
  }
}

Testing Strategy

SSR Test Suite

  1. Render Tests: Each node type renders correct HTML
  2. Hydration Tests: Client picks up server state correctly
  3. SEO Tests: Meta tags present in rendered output
  4. Error Tests: Graceful fallback on component errors
  5. Performance Tests: SSR response times under load

Validation Checklist

  • All visual nodes render without errors
  • Page Router navigates correctly
  • SEO meta tags injected properly
  • Hydration completes without mismatch warnings
  • Fallback to CSR works when SSR fails
  • Build scripts continue to work
  • Cloud functions unaffected

Open Questions

  1. React 19 First? Should we complete React 19 migration before SSR work? The SSR code uses React 17's renderToString - React 19 has different streaming APIs.

  2. Streaming SSR? React 18+ supports streaming SSR with Suspense. Should we support this for better TTFB?

  3. Edge Runtime? Should we support edge deployment (Cloudflare Workers, Vercel Edge) for lower latency?

  4. Partial Hydration? Should we implement islands architecture for selective hydration?

  5. Preview in Editor? Can we show SSR output in the editor for SEO debugging?

Success Metrics

  • Adoption: % of deploys using SSR/SSG modes
  • SEO Improvement: User-reported search ranking changes
  • Performance: Core Web Vitals improvements (LCP, FID, CLS)
  • Developer Experience: Time to deploy with SSR enabled

References

  • Original SSR code: packages/noodl-viewer-react/static/ssr/
  • SEO API docs: javascript/reference/seo/README.md
  • Build scripts: javascript/extending/build-script/
  • Deploy infrastructure: packages/noodl-editor/src/editor/src/utils/compilation/