7.1 KiB
Sidebar Injection Architecture
Overview
The soleprint sidebar is injected into managed app pages using a hybrid nginx + JavaScript approach. This allows any frontend framework (React, Next.js, static HTML) to receive the sidebar without code modifications.
How It Works
┌─────────────────────────────────────────────────────────────────┐
│ Browser Request │
│ http://room.spr.local.ar/ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Nginx │
│ │
│ 1. Routes /spr/* → soleprint:PORT (sidebar assets + API) │
│ 2. Routes /* → frontend:PORT (app pages) │
│ 3. Injects CSS+JS into HTML responses via sub_filter │
│ │
│ sub_filter '</head>' │
│ '<link rel="stylesheet" href="/spr/sidebar.css"> │
│ <script src="/spr/sidebar.js" defer></script></head>'; │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Browser Renders │
│ │
│ 1. Page loads with injected CSS (sidebar styles ready) │
│ 2. sidebar.js executes (deferred, after DOM ready) │
│ 3. JS fetches /spr/api/sidebar/config (room name, auth, etc) │
│ 4. JS creates sidebar DOM elements and injects into page │
│ 5. Sidebar appears on left side, pushes content with margin │
└─────────────────────────────────────────────────────────────────┘
Key Design Decisions
Why nginx sub_filter instead of modifying app code?
- Framework agnostic: Works with any frontend (Next.js, React, Vue, static HTML)
- No app changes needed: The managed app doesn't need to know about soleprint
- Easy to disable: Just access
room.local.arinstead ofroom.spr.local.ar
Why inject into </head> instead of </body>?
Next.js and other streaming SSR frameworks may not include </body> in the initial HTML response. The </head> tag is always present, so we inject both CSS and JS there using defer to ensure JS runs after DOM is ready.
Why JavaScript injection instead of iframe?
- No iframe isolation issues: Sidebar can interact with page if needed
- Better UX: No double scrollbars, native feel
- Simpler CSS: Just push content with
margin-left
Nginx Configuration
For Local Development (system nginx)
Each room needs entries in /etc/nginx/sites-enabled/:
# room.spr.local.ar - app with sidebar
server {
listen 80;
server_name room.spr.local.ar;
# Soleprint assets and API
location /spr/ {
proxy_pass http://127.0.0.1:SOLEPRINT_PORT/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Backend API (if applicable)
location /api/ {
proxy_pass http://127.0.0.1:BACKEND_PORT/api/;
# ... headers
}
# Frontend with sidebar injection
location / {
proxy_pass http://127.0.0.1:FRONTEND_PORT;
proxy_set_header Host $host;
proxy_set_header Accept-Encoding ""; # Required for sub_filter
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Inject sidebar
sub_filter '</head>' '<link rel="stylesheet" href="/spr/sidebar.css"><script src="/spr/sidebar.js" defer></script></head>';
sub_filter_once off;
sub_filter_types text/html;
}
}
# room.local.ar - app without sidebar (direct access)
server {
listen 80;
server_name room.local.ar;
# ... same as above but without sub_filter
}
For Docker/AWS (nginx container)
The nginx config lives in cfg/<room>/soleprint/nginx/local.conf and uses docker service names instead of localhost ports:
location /spr/ {
proxy_pass http://soleprint:8000/spr/;
}
location / {
proxy_pass http://frontend:3000;
# ... sub_filter same as above
}
Port Allocation
Each room should use unique ports to allow concurrent operation:
| Room | Soleprint | Frontend | Backend |
|---|---|---|---|
| amar | 12000 | 3000 | 8001 |
| dlt | 12010 | 3010 | - |
| sample | 12020 | 3020 | 8020 |
Sidebar Assets
The sidebar consists of two files served by soleprint:
/sidebar.css- Styles for the sidebar (dark theme, positioning)/sidebar.js- Self-contained JS that fetches config and renders sidebar
Source location: soleprint/station/tools/sbwrapper/
Sidebar Config API
The sidebar JS fetches configuration from /spr/api/sidebar/config:
{
"room": "amar",
"soleprint_base": "/spr",
"auth_enabled": true,
"tools": {
"artery": "/spr/artery",
"atlas": "/spr/atlas",
"station": "/spr/station"
},
"auth": {
"login_url": "/spr/artery/google/oauth/login",
"logout_url": "/spr/artery/google/oauth/logout"
}
}
Troubleshooting
Sidebar not appearing
- Check if soleprint is running:
curl http://room.spr.local.ar/spr/sidebar.js - Check browser console for
[Soleprint]messages - Verify nginx has
Accept-Encoding ""set (required for sub_filter) - Hard refresh (Ctrl+Shift+R) to clear cached HTML
Wrong room config showing
Each room needs its own docker network (room_network) to isolate services. Check NETWORK_NAME in .env files and ensure containers are on correct networks.
sub_filter not working
- Ensure
proxy_set_header Accept-Encoding ""is set - Check that response is
text/html(sub_filter_types) - Streaming SSR may not have
</body>, use</head>injection instead