Hello @AQuirky
Thank you for your detailed response — we truly appreciate it, and it’s been very helpful.
It seems my initial response was correct: you cannot use DevTools in production. The Builder.io DevTools are designed primarily for local development environments. They’re intended to help you quickly set up and integrate Builder into your codebase, enabling you to develop, test, and preview your components and pages seamlessly during development.
Once your app is deployed to production (e.g., on Azure or any other server), it is served from your domain address rather than localhost
, which is where DevTools are designed to operate.
That said, I may have found a workaround to get similar functionality in production.
Builder.io DevTools are currently configured to only work in development mode. Reviewing the Webpack plugin code, I found this line:
this.opts.enabled=process.env.NODE_ENV!=="production"
This means the DevTools are automatically disabled when NODE_ENV
is set to "production"
. We attempted to manually enable it in production but were not able to get it working.
However, you can create a custom component (for example, a DevTools button) that opens Builder content in production. Here’s an example I tested:
Step 1: Create the Custom Dev Tools Component
File: src/components/BuilderDevTools.tsx
'use client';
import { useEffect } from 'react';
export default function BuilderDevTools() {
useEffect(() => {
// Only inject in production for testing
if (process.env.NODE_ENV === 'production') {
console.log('Attempting to load Builder.io dev tools...');
// Check if dev tools are already loaded
if (window.builderDevToolsLoaded) {
console.log('Builder.io dev tools already loaded');
return;
}
// In production, we need to inject the dev tools script directly
// since the dev tools server is not running
const injectDevTools = () => {
// Create the dev tools script content
const devToolsScript = `
(function() {
if (typeof window !== 'undefined' && !window.builderDevToolsLoaded) {
window.builderDevToolsLoaded = true;
// Create the floating toggle button
const floatingButton = document.createElement('div');
floatingButton.id = 'builder-floating-toggle';
floatingButton.style.cssText = \`
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background: #00d4ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 9998;
transition: all 0.3s ease;
border: 2px solid #fff;
font-family: system-ui, -apple-system, sans-serif;
font-weight: bold;
color: #000;
font-size: 18px;
\`;
floatingButton.innerHTML = 'B';
document.body.appendChild(floatingButton);
// Create the dev tools menu element
const menu = document.createElement('builder-dev-tools-menu');
menu.setAttribute('data-version', '1.9.14');
menu.setAttribute('aria-hidden', 'true');
document.body.appendChild(menu);
// Enhanced dev tools functionality
const devTools = {
isOpen: false,
contentId: null,
modelName: null,
init: function() {
console.log('Builder.io dev tools initialized in production');
this.detectContentInfo();
this.createUI();
this.setupEventListeners();
},
setupEventListeners: function() {
// Floating button click event
const floatingBtn = document.getElementById('builder-floating-toggle');
if (floatingBtn) {
floatingBtn.addEventListener('click', () => {
this.toggleMenu();
// Add click effect
floatingBtn.style.transform = 'scale(0.95)';
setTimeout(() => {
floatingBtn.style.transform = 'scale(1)';
}, 150);
});
// Add hover effects
floatingBtn.addEventListener('mouseenter', () => {
floatingBtn.style.transform = 'scale(1.1)';
floatingBtn.style.boxShadow = '0 6px 25px rgba(0,0,0,0.4)';
});
floatingBtn.addEventListener('mouseleave', () => {
floatingBtn.style.transform = 'scale(1)';
floatingBtn.style.boxShadow = '0 4px 20px rgba(0,0,0,0.3)';
});
}
},
detectContentInfo: function() {
// Find the first div with builder-content-id attribute (without data- prefix)
const contentElement = document.querySelector('div[builder-content-id]');
if (contentElement) {
this.contentId = contentElement.getAttribute('builder-content-id');
this.modelName = contentElement.getAttribute('builder-model');
console.log('Detected Builder content ID:', this.contentId);
console.log('Detected Builder model:', this.modelName);
} else {
console.log('No Builder content ID found in DOM');
// Also try with data- prefix as fallback
const fallbackElement = document.querySelector('div[data-builder-content-id]');
if (fallbackElement) {
this.contentId = fallbackElement.getAttribute('data-builder-content-id');
this.modelName = fallbackElement.getAttribute('data-builder-model');
console.log('Detected Builder content ID (fallback):', this.contentId);
console.log('Detected Builder model (fallback):', this.modelName);
}
}
},
createUI: function() {
if (menu) {
menu.style.display = 'none';
menu.style.position = 'fixed';
menu.style.top = '10px';
menu.style.right = '10px';
menu.style.zIndex = '9999';
menu.style.background = '#1a1a1a';
menu.style.border = '1px solid #333';
menu.style.padding = '0';
menu.style.borderRadius = '8px';
menu.style.boxShadow = '0 4px 20px rgba(0,0,0,0.3)';
menu.style.fontFamily = 'system-ui, -apple-system, sans-serif';
menu.style.fontSize = '12px';
menu.style.color = '#fff';
menu.style.minWidth = '300px';
menu.style.maxHeight = '80vh';
menu.style.overflow = 'auto';
menu.innerHTML = \`
<div style="padding: 12px; border-bottom: 1px solid #333; display: flex; justify-content: space-between; align-items: center;">
<div style="font-weight: 600; color: #00d4ff;">Builder.io Dev Tools</div>
<button id="builder-dev-tools-toggle" style="background: #00d4ff; border: none; color: #000; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 10px;">Close</button>
</div>
<div id="builder-dev-tools-content" style="padding: 12px;">
<div style="margin-bottom: 12px;">
<div style="font-weight: 600; margin-bottom: 8px;">Current Page</div>
<div style="color: #888;">\${window.location.pathname}</div>
</div>
<div style="margin-bottom: 12px;">
<div style="font-weight: 600; margin-bottom: 8px;">Content ID</div>
<div id="builder-content-id" style="color: #888; font-family: monospace; font-size: 11px;">\${this.contentId || 'Not detected'}</div>
</div>
<div style="margin-bottom: 12px;">
<div style="font-weight: 600; margin-bottom: 8px;">Model Name</div>
<div id="builder-model-name" style="color: #888; font-family: monospace; font-size: 11px;">\${this.modelName || 'Not detected'}</div>
</div>
<div style="margin-bottom: 12px;">
<div style="font-weight: 600; margin-bottom: 8px;">Actions</div>
<button id="builder-open-builder" style="background: #00d4ff; border: none; color: #000; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; margin-bottom: 8px;">Open Builder</button>
<button id="builder-detect-content" style="background: #333; border: 1px solid #555; color: #fff; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 11px;">Re-detect Content</button>
</div>
</div>
\`;
// Add event listeners
setTimeout(() => {
const toggleBtn = document.getElementById('builder-dev-tools-toggle');
const openBuilderBtn = document.getElementById('builder-open-builder');
const detectContentBtn = document.getElementById('builder-detect-content');
if (toggleBtn) {
toggleBtn.addEventListener('click', () => this.toggleMenu());
}
if (openBuilderBtn) {
openBuilderBtn.addEventListener('click', () => this.openBuilder());
}
if (detectContentBtn) {
detectContentBtn.addEventListener('click', () => this.redetectContent());
}
}, 100);
}
},
showMenu: function() {
if (menu) {
menu.style.display = 'block';
this.isOpen = true;
}
},
hideMenu: function() {
if (menu) {
menu.style.display = 'none';
this.isOpen = false;
}
},
toggleMenu: function() {
if (this.isOpen) {
this.hideMenu();
} else {
this.showMenu();
}
},
redetectContent: function() {
this.detectContentInfo();
const contentIdEl = document.getElementById('builder-content-id');
const modelNameEl = document.getElementById('builder-model-name');
if (contentIdEl) {
contentIdEl.textContent = this.contentId || 'Not detected';
}
if (modelNameEl) {
modelNameEl.textContent = this.modelName || 'Not detected';
}
console.log('Re-detected content ID:', this.contentId);
console.log('Re-detected model name:', this.modelName);
},
openBuilder: function() {
let builderUrl;
if (this.contentId) {
// Open specific content in Builder.io (without API key)
builderUrl = \`https://builder.io/content/\${this.contentId}\`;
console.log('Opening specific content in Builder:', builderUrl);
} else {
// Fallback to opening the current page
const currentUrl = window.location.href;
builderUrl = \`https://builder.io/content?url=\${encodeURIComponent(currentUrl)}\`;
console.log('Opening current page in Builder:', builderUrl);
}
window.open(builderUrl, '_blank');
}
};
window.BuilderDevTools = devTools;
// Auto-initialize
setTimeout(() => {
devTools.init();
}, 1000);
}
})();
`;
// Create and inject the script
const script = document.createElement('script');
script.textContent = devToolsScript;
script.async = true;
document.head.appendChild(script);
console.log('Builder.io dev tools script injected');
};
// Try to inject the dev tools
try {
injectDevTools();
} catch (error) {
console.error('Failed to inject Builder.io dev tools:', error);
}
return () => {
// Cleanup: remove the dev tools menu and floating button if they exist
const existingMenu = document.querySelector('builder-dev-tools-menu');
const existingButton = document.getElementById('builder-floating-toggle');
if (existingMenu) {
existingMenu.remove();
}
if (existingButton) {
existingButton.remove();
}
window.builderDevToolsLoaded = false;
};
}
}, []);
return null;
}
// Add TypeScript declarations
declare global {
interface Window {
builderDevToolsLoaded?: boolean;
BuilderDevTools?: {
init: () => void;
showMenu: () => void;
hideMenu: () => void;
toggleMenu: () => void;
openBuilder: () => void;
detectContentInfo: () => void;
redetectContent: () => void;
};
}
}
Step 2: Add Component to Layout
File: src/app/layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import BuilderDevTools from "@/components/BuilderDevTools";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<BuilderDevTools />
{children}
</body>
</html>
);
}
Step 3: Configure Next.js for Development
File: next.config.ts
import BuilderDevTools from "@builder.io/dev-tools/next";
import type { NextConfig } from "next";
const nextConfig: NextConfig = BuilderDevTools({
enabled: true, // Enable dev tools in all environments
})({
/* config options here */
});
export default nextConfig;
Step 4: Test in Development
npm run dev
Step 5: Test in Production
npm run build
npm start
Here is the loom
Hope this helps!
Thanks