Remote Sync
Overview
Remote sync allows a local UDL server to fetch and mirror data from a production UDL server. This is useful for local development when you want to work with real production data without configuring CMS credentials locally.
Architecture
┌─────────────────────────────────────────────────────────────────────────┐
│ PRODUCTION SERVER │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Contentful │────▶│ │────▶│ │ │
│ │ Shopify │ │ Plugins │ │ Node Store │ │
│ │ Custom CMS │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └───────────┬─────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ │ │
│ /_sync /ws (optional) │
│ │ │ │
└──────────────────────────────────────────┼───────────────────────┼──────┘
│ │
│ Initial Fetch │ Real-time
│ (all nodes) │ Updates
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────────────────┐
│ LOCAL DEV SERVER │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Node Store │ │
│ │ (mirror of production) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ /graphql │
│ │ │
│ ▼ │
│ Your Application │
│ │
└──────────────────────────────────────────────────────────────────────────┘
How It Works
Step 1: Initial Sync
When the local server starts with remote.url configured:
Local Server Remote Server
│ │
│ GET /_sync?since=1970-01-01T00:00:00Z │
│─────────────────────────────────────────▶│
│ │
│ { updated: [...], deleted: [] } │
│◀─────────────────────────────────────────│
│ │
│ Populate local node store │
│ │
The local server requests all nodes by using the epoch timestamp. The remote server returns every node in its store.
Step 2: WebSocket Connection (Optional)
If the remote server has WebSockets enabled, the local server connects for real-time updates:
Local Server Remote Server
│ │
│ WebSocket: ws://remote/ws │
│─────────────────────────────────────────▶│
│ │
│ { type: "subscribe", data: "*" } │
│─────────────────────────────────────────▶│
│ │
│ { type: "subscribed", types: [...] } │
│◀─────────────────────────────────────────│
│ │
Step 3: Real-time Updates
When content changes on the remote server, updates flow automatically:
CMS Webhook Remote Server Local Server
│ │ │
│ Content Updated │ │
│──────────────────────────▶│ │
│ │ │
│ │ { type: "node:updated", │
│ │ nodeId: "...", │
│ │ data: {...} } │
│ │───────────────────────────▶│
│ │ │
│ │ Update local store│
│ │ │
Configuration
Basic Setup
import { defineConfig } from 'universal-data-layer';
export const config = defineConfig({
port: 4000,
remote: {
url: 'https://production-udl.example.com',
},
});
What Gets Skipped
When remote.url is set, the local server:
- Skips plugin loading (no
sourceNodescalls) - Skips webhook handlers (remote handles those)
- Keeps local GraphQL endpoint active
- Keeps local codegen working (if configured)
Shared Config Files
You can use the same config file for both production and local development. UDL automatically detects which mode to use by checking if the remote server is reachable:
export const config = defineConfig({
port: 4000,
plugins: ['./plugins/my-source'],
remote: {
url: 'http://localhost:4000',
},
});
When production starts first:
Production Local Dev (not started yet)
│
│ Check: Is http://localhost:4000 reachable?
│ ❌ No (nothing running yet)
│
│ Load plugins, source data
│ Start server on :4000
│
When local dev starts after:
Production (running on :4000) Local Dev
│
│ Check: Is http://localhost:4000 reachable?
│ ✅ Yes (production is running)
│
│ Sync from remote instead of loading plugins
│
This works for any setup:
- Same machine, same port (production starts first)
- Same machine, different ports
- Different machines (local dev connects to remote production)
- Multi-tenant setups on localhost
Use Cases
Local Development with Production Data
Work with real content without CMS credentials:
export const config = defineConfig({
remote: {
url: process.env.PRODUCTION_UDL_URL,
},
codegen: {
output: './generated',
},
});
CI/CD Preview Builds
Preview deployments can sync from production:
export const config = defineConfig({
remote: {
// Production UDL for preview builds
url: process.env.CI ? process.env.PRODUCTION_UDL_URL : undefined,
},
plugins: process.env.CI ? [] : [
// Local dev uses plugins directly
{ name: '@universal-data-layer/plugin-source-contentful', options: {...} },
],
});
Staging Environments
Staging can mirror production data:
Production UDL ──────▶ Staging UDL (remote sync)
│
└─────────────▶ Local Dev (remote sync)
WebSocket Behavior
The WebSocket connection is optional and graceful:
| Remote Server State | Local Server Behavior |
|---|---|
| WebSocket enabled | Connects and receives real-time updates |
| WebSocket disabled | Works fine, just no real-time updates |
| WebSocket unavailable | Logs message, continues without it |
To enable WebSocket on the production server:
export const config = defineConfig({
plugins: [...],
remote: {
websockets: {
enabled: true,
},
},
});
Comparison: Remote Sync vs Local Plugins
| Aspect | Remote Sync | Local Plugins |
|---|---|---|
| CMS credentials | Not needed locally | Required locally |
| Data freshness | Mirrors production | Direct from CMS |
| Webhook handling | Remote server | Local server |
| Network dependency | Requires remote UDL | Requires CMS APIs |
| Setup complexity | Minimal | Requires plugin config |
Troubleshooting
Connection Refused
Error: Failed to fetch from remote UDL: ECONNREFUSED
- Verify the remote URL is correct
- Check if the remote server is running
- Ensure network/firewall allows the connection
Empty Node Store
If no nodes are loaded:
- Check remote server has data:
curl https://remote/_sync?since=1970-01-01T00:00:00Z - Verify the response contains
updatedarray with nodes - Check for errors in local server logs
WebSocket Not Connecting
This is normal if the remote server doesn't have WebSocket enabled. The local server will log:
📡 Remote WebSocket not available (this is fine if remote has websockets disabled)
Next Steps
- Production Deployment - Deploy the production server
- Webhook Setup - Configure CMS webhooks on production