RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-03-26Advanced

Rork Max × Server-Driven UI— Control Your App's UI in Real Time from the Backend

Learn how to implement Server-Driven UI (SDUI) in Rork Max. Deliver UI components as JSON from your backend and dynamically update app layouts without app store reviews.

rork-max40server-driven-uiarchitecture12react-native12dynamic-uibackend9JSONdesign-pattern

Premium Article

What Is Server-Driven UI — and Why It Matters for Mobile Development

In traditional mobile development, every UI change requires a new app store submission and review cycle. This creates a frustrating gap between what your business needs and what you can actually ship. Server-Driven UI (SDUI) solves this problem at its root.

The core idea is elegantly simple: define your UI layout, structure, and display conditions as a JSON response from the server, and let the client (your app) interpret that JSON to render the appropriate components. Companies like Airbnb, Shopify, and Lyft use this pattern in production to power instant A/B test switching and personalized UI delivery.

Rork Max generates native apps built on React Native and Expo, making it an excellent fit for the SDUI pattern. In this guide, we'll walk through the design principles, JSON schema, rendering engine, and backend architecture you need to bring SDUI into your Rork Max project.

SDUI Design Principles — The Three Core Layers

An SDUI architecture consists of three distinct layers.

Layer 1: Schema Definition (Contract Layer)

This is the "contract" between your server and client — the JSON schema that defines what components exist and how they're configured. This schema is the backbone of SDUI and deserves careful thought.

// types/sdui.ts — SDUI component type definitions
type SDUIComponentType =
  | 'hero_banner'
  | 'product_grid'
  | 'text_block'
  | 'cta_button'
  | 'carousel'
  | 'feature_list'
  | 'testimonial'
  | 'spacer';
 
interface SDUIComponent {
  id: string;
  type: SDUIComponentType;
  props: Record<string, unknown>;
  children?: SDUIComponent[];
  visibility?: {
    conditions: VisibilityCondition[];
    operator: 'AND' | 'OR';
  };
  analytics?: {
    impressionEvent: string;
    tapEvent: string;
  };
}
 
interface VisibilityCondition {
  field: string;        // e.g., "user.plan", "device.platform"
  operator: 'eq' | 'neq' | 'gt' | 'lt' | 'in';
  value: string | number | string[];
}
 
// Root type for the server response
interface SDUIScreenResponse {
  screenId: string;
  version: number;
  ttl: number;          // Cache TTL in seconds
  components: SDUIComponent[];
  metadata: {
    title: string;
    analytics_screen_name: string;
  };
}

Layer 2: Rendering Engine (Rendering Layer)

The rendering engine receives JSON and maps it to the corresponding React Native components.

// components/sdui/SDUIRenderer.tsx
import React from 'react';
import { View } from 'react-native';
import { HeroBanner } from './HeroBanner';
import { ProductGrid } from './ProductGrid';
import { TextBlock } from './TextBlock';
import { CTAButton } from './CTAButton';
import { Carousel } from './Carousel';
import { FeatureList } from './FeatureList';
 
// Component registry — add new components here
const COMPONENT_REGISTRY: Record<string, React.ComponentType<any>> = {
  hero_banner: HeroBanner,
  product_grid: ProductGrid,
  text_block: TextBlock,
  cta_button: CTAButton,
  carousel: Carousel,
  feature_list: FeatureList,
};
 
interface SDUIRendererProps {
  components: SDUIComponent[];
  context: UserContext;  // User info (plan, device, etc.)
}
 
export function SDUIRenderer({ components, context }: SDUIRendererProps) {
  return (
    <View style={{ flex: 1 }}>
      {components
        .filter((comp) => evaluateVisibility(comp.visibility, context))
        .map((comp) => {
          const Component = COMPONENT_REGISTRY[comp.type];
          if (!Component) {
            // Safely skip unknown components (forward compatibility)
            console.warn(`Unknown SDUI component: ${comp.type}`);
            return null;
          }
          return (
            <Component
              key={comp.id}
              {...comp.props}
              analytics={comp.analytics}
            >
              {comp.children && (
                <SDUIRenderer components={comp.children} context={context} />
              )}
            </Component>
          );
        })}
    </View>
  );
}
 
// Expected output: Components render dynamically based on JSON definitions
// e.g., hero_banner → <HeroBanner title="..." />, product_grid → <ProductGrid items={[...]} />

Layer 3: Data Delivery (Delivery Layer)

The backend serves JSON definitions while the client handles caching for optimal performance.

// hooks/useSDUIScreen.ts — Screen data fetching with cache management
import { useState, useEffect, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
 
const SDUI_CACHE_PREFIX = 'sdui_cache_';
 
export function useSDUIScreen(screenId: string) {
  const [screen, setScreen] = useState<SDUIScreenResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
 
  const fetchScreen = useCallback(async () => {
    try {
      // 1. Check cache first (offline support)
      const cached = await AsyncStorage.getItem(
        `${SDUI_CACHE_PREFIX}${screenId}`
      );
      if (cached) {
        const parsed = JSON.parse(cached);
        const isExpired =
          Date.now() - parsed.cachedAt > parsed.data.ttl * 1000;
        if (!isExpired) {
          setScreen(parsed.data);
          setLoading(false);
          return;
        }
      }
 
      // 2. Fetch latest data from server
      const response = await fetch(
        `https://api.example.com/sdui/screens/${screenId}`,
        {
          headers: {
            'X-App-Version': '2.1.0',
            'X-Platform': Platform.OS,
          },
        }
      );
      const data: SDUIScreenResponse = await response.json();
 
      // 3. Save to cache
      await AsyncStorage.setItem(
        `${SDUI_CACHE_PREFIX}${screenId}`,
        JSON.stringify({ data, cachedAt: Date.now() })
      );
 
      setScreen(data);
    } catch (err) {
      setError(err as Error);
      // Fallback: show stale cache rather than nothing
      const cached = await AsyncStorage.getItem(
        `${SDUI_CACHE_PREFIX}${screenId}`
      );
      if (cached) {
        setScreen(JSON.parse(cached).data);
      }
    } finally {
      setLoading(false);
    }
  }, [screenId]);
 
  useEffect(() => {
    fetchScreen();
  }, [fetchScreen]);
 
  return { screen, loading, error, refetch: fetchScreen };
}

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Understand SDUI design principles and JSON schema definitions from concept to production-ready implementation
Build a release workflow that updates your app's UI instantly without app store review
Apply the same SDUI patterns used by Airbnb and Shopify to your Rork Max projects
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-07-12
Undo That Jumps to the Wrong Place — Designing Edit History You Can Trust
Snapshotting the whole state on every edit crushes memory after a few dozen steps. Here's an undo/redo built on command history plus snapshots, with coalescing, a depth cap, and cross-session persistence, in working TypeScript.
Dev Tools2026-06-23
The Post You Wrote Offline Shows Up Twice — Designing a Send Outbox That Survives Retries
Persisting a queue and replaying it isn't enough — a lost response turns into a duplicate write. Here's a send outbox with idempotency keys, temp-to-server ID remapping, and poison-message quarantine, in working TypeScript.
Dev Tools2026-05-22
Designing an Observability Stack for Rork Max — Unifying Sentry, Crashlytics, and Cloudflare Logs from a Solo Developer's View
A practical observability stack design for apps shipped with Rork Max, covering Sentry, Crashlytics, and Cloudflare Logs role separation, scenario-based incident tracing routes, and how a solo developer can sustain it over years.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →