A production-ready Server-Driven UI implementation for React Native with two approaches:
- 🎯 Hybrid Approach (Recommended) - Mix native code with server-driven chunks
- 📦 Legacy Monolithic Approach - Full app driven by server (for reference)
The hybrid approach lets you mix static React Native code with dynamic server-driven content. This is the recommended approach for production apps.
cd src/sdui/server
npm install
npm run devServer runs on http://localhost:3001
React Native can't use localhost, so you need your machine's IP:
# macOS/Linux
ifconfig | grep "inet " | grep -v 127.0.0.1
# Windows
ipconfig// App.tsx
import { SDUI } from './src/sdui';
export default function App() {
return (
<SDUI.Provider
config={{
baseURL: 'http://192.168.1.XXX:3001', // ← Your IP here
enableCache: true,
cacheTTL: 1000 * 60 * 5, // 5 minutes
}}
>
<MyExistingApp />
</SDUI.Provider>
);
}import { SDUI } from './src/sdui';
function HomeScreen() {
return (
<ScrollView>
{/* Static header - you control */}
<Header title="Home" />
{/* Dynamic feed - server controls */}
<SDUI.Renderer
path="/home/feed.json"
priority="high"
cache={true}
fallback={<Loading />}
/>
{/* Static footer - you control */}
<Footer />
</ScrollView>
);
}That's it! 🎉 See HYBRID_QUICKSTART.md for complete guide.
src/sdui/
├── SDUIApp.tsx # Legacy: Full monolithic app
├── SDUIAppWithNavigation.tsx # Legacy: App with navigation
│
├── context/ # ⭐ NEW: Hybrid API
│ └── SDUIProvider.tsx # Context provider
│
├── core/ # Core systems
│ ├── ComponentRegistry.ts # Component mapping
│ ├── ActionHandler.ts # Action processing
│ ├── WebSocketService.ts # Real-time updates
│ ├── CacheService.ts # Offline caching
│ ├── PayloadValidator.ts # Validation
│ ├── PerformanceMonitor.ts # Performance tracking
│ ├── RetryHelper.ts # Retry logic
│ └── SDUILoader.ts # ⭐ NEW: Chunked loading
│
├── components/
│ ├── SDUIRenderer.tsx # Component renderer
│ ├── Renderer.tsx # ⭐ NEW: Hybrid renderer
│ ├── ErrorBoundary.tsx # Error handling
│ ├── FallbackUI.tsx # Fallback screens
│ └── builtin/ # Built-in components
│ ├── TextComponent.tsx
│ ├── ButtonComponent.tsx
│ ├── ListComponent.tsx
│ ├── HeaderComponent.tsx
│ ├── HeadingComponent.tsx
│ ├── ParagraphComponent.tsx
│ ├── SpacerComponent.tsx
│ ├── ContainerComponent.tsx
│ ├── GridComponent.tsx
│ ├── CardComponent.tsx
│ ├── ImageComponent.tsx
│ └── DividerComponent.tsx
│
├── navigation/ # Navigation support
│ ├── NavigationRenderer.tsx
│ └── SDUIScreen.tsx
│
├── types/ # TypeScript definitions
│ ├── index.ts
│ └── navigation.ts
│
├── server/ # Backend server
│ ├── src/
│ │ ├── server.ts # Express + WebSocket + Chunked endpoints
│ │ └── types.ts
│ ├── package.json
│ └── tsconfig.json
│
├── playground/ # Web-based testing playground
│ ├── index.html
│ ├── index-enhanced.html # Monaco editor version
│ ├── playground.js
│ ├── monaco-playground.js
│ ├── templates-complete.js
│ ├── package.json
│ └── vite.config.js
│
├── examples/ # ⭐ NEW: Hybrid examples
│ ├── HybridApp.tsx # Complete app setup
│ ├── HybridDemoScreen.tsx # Example screens
│ └── README.md # Examples guide
│
└── docs/ # Documentation
├── HYBRID_QUICKSTART.md # ⭐ NEW: Hybrid quick start
├── HYBRID_APPROACH_SPEC.md # ⭐ NEW: Hybrid architecture
├── ARCHITECTURE_PROBLEMS_AND_SOLUTIONS.md # ⭐ NEW: Design decisions
├── CACHING_IMPLEMENTATION.md
├── ERROR_HANDLING_IMPLEMENTATION.md
├── PERFORMANCE_IMPLEMENTATION.md
├── QUICKSTART.md # Legacy quick start
├── README.md
└── ...more docs
When to use:
- Adding SDUI to existing app
- Want control over navigation/structure
- Need mix of static + dynamic content
- Want small, focused updates
Pros:
- ✅ Small payloads (5-20KB per chunk)
- ✅ Fast loading (<100ms)
- ✅ Gradual adoption
- ✅ Low risk
- ✅ Easy to remove
Example:
<SDUI.Provider config={...}>
<MyApp>
<MyHeader />
<SDUI.Renderer path="/feed.json" /> {/* Dynamic */}
<MyFooter />
</MyApp>
</SDUI.Provider>Read more: HYBRID_QUICKSTART.md
When to use:
- Learning/experimentation
- Proof of concept
- Simple demo apps
- Admin panels
Pros:
- ✅ Everything server-driven
- ✅ Good for demos
Cons:
- ❌ Large payloads (100KB-2MB)
- ❌ Slower loading
- ❌ All or nothing
Example:
import SDUIApp from './src/sdui/SDUIApp';
export default SDUIApp;Read more: QUICKSTART.md
Wraps your app to enable SDUI functionality.
<SDUI.Provider
config={{
baseURL: string; // Required: API base URL
enableCache?: boolean; // Default: true
cacheTTL?: number; // Default: 1 hour (ms)
enableWebSocket?: boolean; // Default: false
wsURL?: string; // Required if WebSocket enabled
timeout?: number; // Default: 10000ms
retryAttempts?: number; // Default: 3
onError?: (error, path) => void;
cacheVersion?: string;
}}
>
{children}
</SDUI.Provider>Renders server-driven content chunks.
<SDUI.Renderer
path="/home/feed.json" // Required: JSON endpoint
// Loading
priority="high" // 'high' | 'normal' | 'low'
lazy={false} // Wait until visible
// Caching
cache={true} // Enable caching
cacheTTL={300000} // Cache TTL (ms)
// Fallbacks
fallback={<Loading />} // Loading state
errorFallback={<Error />} // Error state
// Callbacks
onLoad={(data) => {}} // On success
onError={(error) => {}} // On error
// Advanced
liveUpdate={false} // WebSocket updates
props={{}} // Props for components
context={{}} // Context for server
/>Access SDUI context for manual control.
const { loader, cache, config, websocket } = SDUI.useSDUI();
// Manual loading
const data = await loader.load('/path.json', {
priority: 'high',
cache: false,
});
// Preloading
await loader.preload('/next-screen.json');
await loader.preloadBatch(['/a.json', '/b.json']);
// Cache management
await loader.clearCache('/path.json'); // Clear specific
await loader.clearCache(); // Clear all
await loader.invalidate('/path.json'); // Invalidate + reloadAll components work with both hybrid and legacy approaches.
text- Basic text displayheading- Heading text (H1-H6)paragraph- Paragraph textimage- Image display
container- Flex containergrid- Grid layoutcard- Card containerspacer- Spacing elementdivider- Visual divider
button- Pressable button (primary/secondary/text)list- Scrollable listheader- App header with title/back button
{
"components": [
{
"id": "heading-1",
"type": "heading",
"data": {
"text": "Welcome!",
"level": 1
}
},
{
"id": "button-1",
"type": "button",
"data": {
"title": "Click Me",
"variant": "primary"
},
"actions": [{
"type": "show_alert",
"trigger": "onPress",
"payload": {
"title": "Hello!",
"message": "Button clicked"
}
}]
}
]
}Display native alert dialog.
{
"type": "show_alert",
"payload": {
"title": "Hello",
"message": "World"
}
}Navigate to screen.
{
"type": "navigate",
"payload": {
"screen": "Details",
"params": { "id": 123 }
}
}Fetch data from API.
{
"type": "fetch_data",
"payload": {
"endpoint": "/api/items",
"onSuccess": {
"type": "update_component",
"componentId": "list-1"
}
}
}Submit form data.
{
"type": "submit_form",
"payload": {
"formData": { ... }
}
}Update component dynamically.
{
"type": "update_component",
"payload": {
"componentId": "text-1",
"updates": { ... }
}
}Open external URL.
{
"type": "open_url",
"payload": {
"url": "https://example.com"
}
}// server/src/server.ts
app.post('/home/feed.json', (req, res) => {
const { context } = req.body;
res.json({
components: [
{ id: 'heading-1', type: 'heading', data: { ... } },
{ id: 'button-1', type: 'button', data: { ... } },
],
metadata: {
version: '1.0.0',
timestamp: new Date().toISOString(),
},
});
});Demo endpoints included:
POST /home/feed.json- Home feedPOST /profile/actions.json- Profile actionsPOST /marketing/banner.json- Marketing banner (A/B test)
app.post('/api/sdui/screen', (req, res) => {
const { screenId, context } = req.body;
// Returns full screen payload
});
app.post('/api/sdui/action', (req, res) => {
const { action, context } = req.body;
// Processes actions
});
app.post('/api/sdui/navigation', (req, res) => {
const { userId, platform } = req.body;
// Returns navigation config
});Same process for both approaches.
// components/builtin/MyComponent.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { BaseComponentProps } from '../../types';
export const MyComponent: React.FC<BaseComponentProps> = ({ data, style }) => {
return (
<View style={style}>
<Text>{data.message}</Text>
</View>
);
};// SDUIApp.tsx or your registration file
import { MyComponent } from './components/builtin/MyComponent';
const registry = ComponentRegistry.getInstance();
registry.register('my_component', MyComponent);{
"id": "custom-1",
"type": "my_component",
"data": {
"message": "Hello from custom component!"
}
}Web-based playground for testing SDUI JSON in real-time.
cd src/sdui/playground
npm install
npm run devOpens at http://localhost:5173
- Monaco Editor - Full TypeScript/JSON intellisense
- Live Preview - See component structure
- JSON Validation - Real-time schema validation
- Templates - 10+ pre-built examples
- Send to App - Push to React Native via WebSocket
- Stats Dashboard - Payload size, component count
- Select a template or write custom JSON
- Edit in Monaco editor
- Click "Send to App"
- Watch your React Native app update instantly ✨
All modules use prefixed logging:
[SDUIProvider]- Provider lifecycle[SDUILoader]- Loading & caching[SDUI.Renderer]- Rendering chunks[ComponentRegistry]- Component registration[ActionHandler]- Action execution[WebSocketService]- WebSocket events[CacheService]- Cache operations[PayloadValidator]- Validation results
- Check component is registered
- Verify JSON is valid
- Check console for validation errors
- Test endpoint in browser
- Verify server is running (
npm run dev) - Check IP address (not localhost for React Native)
- Test endpoint:
http://192.168.1.XXX:3001/home/feed.json - Check firewall settings
- Clear cache:
loader.clearCache() - Try
cache={false}to bypass - Check TTL hasn't expired
- Verify cache version
- Ensure server is running
- Check IP address matches
- Verify
enableWebSocket: true - Check firewall allows port 3001
- HYBRID_QUICKSTART.md - Hybrid approach (recommended)
- QUICKSTART.md - Legacy approach
- HYBRID_APPROACH_SPEC.md - Hybrid system design
- ARCHITECTURE_PROBLEMS_AND_SOLUTIONS.md - Design decisions & trade-offs
- CACHING_IMPLEMENTATION.md - Caching system
- ERROR_HANDLING_IMPLEMENTATION.md - Error handling
- PERFORMANCE_IMPLEMENTATION.md - Performance features
- examples/README.md - Hybrid examples
- examples/HybridDemoScreen.tsx - Complete demos
<SDUI.Renderer
path="/marketing/banner.json"
cache={false} // Always fresh for A/B tests
priority="low"
lazy={true}
/><SDUI.Renderer
path="/home/feed.json"
cache={true}
cacheTTL={300000} // 5 minutes
priority="high"
liveUpdate={true} // Real-time updates
/><SDUI.Renderer
path={`/profile/${userId}/actions.json`}
props={{ userId }}
cache={true}
/>✅ Good for:
- Marketing content & promos
- Feature discovery flows
- A/B testing
- Dynamic forms
- Content feeds
- Frequently changing content
❌ Not good for:
- Core navigation structure
- Authentication flows
- Performance-critical screens
- Complex business logic
- Real-time interactions
- <5KB: Load immediately, high priority
- 5-10KB: Load on demand, normal priority
- 10-20KB: Lazy load, normal priority
- 20-50KB: Lazy load, low priority
- >50KB: Split into multiple chunks
// Stable content - cache 1 hour
<SDUI.Renderer path="/settings.json" cacheTTL={3600000} />
// Dynamic content - cache 5 min
<SDUI.Renderer path="/feed.json" cacheTTL={300000} />
// Live content - no cache
<SDUI.Renderer path="/promo.json" cache={false} />// Above fold - high
<SDUI.Renderer path="/hero.json" priority="high" />
// Below fold - normal
<SDUI.Renderer path="/content.json" priority="normal" />
// Off screen - low + lazy
<SDUI.Renderer path="/footer.json" priority="low" lazy={true} />import SDUIApp from './src/sdui/SDUIApp';
export default SDUIApp;import { SDUI } from './src/sdui';
export default function App() {
return (
<SDUI.Provider config={{ baseURL: '...' }}>
<NavigationContainer>
<Tabs>
<Tab.Screen name="Home" component={HomeScreen} />
</Tabs>
</NavigationContainer>
</SDUI.Provider>
);
}
function HomeScreen() {
return (
<View>
<Header />
<SDUI.Renderer path="/home/feed.json" />
<Footer />
</View>
);
}- ✅ 133x smaller payloads (15KB vs 2MB)
- ✅ 100x faster parsing (5ms vs 500ms)
- ✅ Better caching (per-chunk vs all-or-nothing)
- ✅ Mix static + dynamic
- ✅ Gradual adoption
- ✅ Easier to remove
- Initial load: <100ms (from cache)
- Network load: <500ms (fresh)
- Chunk size: <20KB average
- Cache hit rate: >80%
- Parse time: <10ms
- Initial load: 500ms-2s
- Network load: 2-5s
- Payload size: 100KB-2MB
- Cache hit rate: Variable
- Parse time: 100-500ms
- ✅ Chunked loading (hybrid)
- ✅ Smart caching with TTL
- ✅ Request deduplication
- ✅ Priority-based loading
- ✅ Lazy loading
- ✅ Retry with exponential backoff
- ✅ Payload validation
- ✅ Performance monitoring
- ✅ Error boundaries
- ✅ WebSocket live updates
- ✅ Offline support
- ✅ 12 built-in components
- ✅ 6 built-in actions
- ✅ Full TypeScript support
- ✅ Web playground with Monaco editor
- ✅ Complete examples
- ✅ Comprehensive docs
- ✅ Console logging
- ✅ Error fallbacks
- ✅ Hot reload support
MIT
- Read HYBRID_QUICKSTART.md
- Start the server
- Try the examples
- Add one
<SDUI.Renderer>to your app - Iterate!
- Add custom components
- Create custom actions
- Build your own endpoints
- Implement A/B testing
- Add WebSocket updates
- Presentation system (modals, bottom sheets)
- Progressive loading (smart prefetch)
- Context-aware manifests (auth state)
- Component-level lazy loading
- Delta updates
Built with ❤️ using React Native, TypeScript, Express, and WebSockets