Builder.io Editor not working when deploying to Azure

I created a Nextjs integration of buider.io very similar to tutorial: Builder 101: Developer Quickstart . If start it with “npm run dev” it works fine. I see the Builder button on my page and I can open the editor on the page.

But now I want to deploy to Azure. I struggled with the Nextjs deployment and the only way I got it to work is with the “standalone” deployment. So my next.config.js file looks like this…

import BuilderDevTools from "@builder.io/dev-tools/next";

import type { NextConfig } from "next";

const nextConfig: NextConfig = BuilderDevTools()({

  output: 'standalone',

  /* config options here */

});



export default nextConfig;

So I deployed this to Azure and it works up to a point. The page is properly created from the builder components, but there is no builder button.

I have a feeling the reason is that this is because the page was statically rendered during the build and now the page has no dynamic content. So no builder button and no responding to changes to the components.

So how do I properly deploy a Builder.io Nextjs integration to Azure?

Hello @AQuirky

I wanted to clarify that @builder.io/dev-tools is intended only for local development. In production, the Builder button is hidden by design for both security and performance reasons.

From your description, it sounds like your Azure deployment is currently generating fully static HTML at build time. In that case, Builder cannot re-fetch content dynamically during editing, so you won’t see changes or the overlay in preview mode.

As an alternative for production, you can also use the Builder.io Chrome extension to open any builder made live page directly in the Builder editor. This provides a quick way to start editing without needing the dev-tools package or local environment running.

Thanks,

I think you are wrong. I see other people in the forums that are trying to do exactly what I am doing. See for example Custom React components don't show up with production Next.js build So in this post the OP is deploying to a production environment (he does not say which, but could be Azure). He says that the live editing is working, but his custom components are not registered. So this is a problem I have yet to see, because I am not that far along…I don’t have live editing working.

So how do I get live editing working in production? I’ve gotten a little further since my last post. I changed the start to…
```
"


"start": "set PORT=8080 && node_modules/next/dist/bin/next start",

Now my pages are no longer statically rendered and so whenever I change the components in the editor and publish, they show up on the rendered page.

I also had to change my Azure GitHub deployment script to add zip and unzip steps…

      - name: Zip artifact for deployment
        run: zip release.zip .env .next ./* -r

with a corresponding unzip step in the deployment script…

      - name: Unzip artifact for deployment
        run: unzip release.zip

This is necessary because the GitHub deployment script does not copy files and directories that start with “.” and “.env” and “.next” are critical.

So how do I get live editing editing working in production? Don’t tell me it can’t work. Your customer ozmoroz has got it working. You guys are something else. Your documentation is nothing but marketing BS and says “Builder IO can do EVERYTHING!”. Meanwhile support constantly tells me “Builder IO can do NOTHING!”.

So it seems that I need to get the builder server running in Azure. Apparently the modified start command does not do that.

And don’t tell me that this is some weird deployment scenario that no one needs. This should be a very common scenario. I want users to be able to edit their own landing pages. These users are not developers. They don’t have a “local development environment”. This is what your marketing BS says is the primary feature of Builder.io: you can modify pages without being a developer! So please, please, help me get this going.

Hello @AQuirky,

Thank you for your detailed follow-up. I think there’s been some miscommunication earlier, which may have caused confusion about your scenario.

From what you’ve described, your component registration should work fine once deployed to production, provided the integration is set up correctly. However, your earlier messages didn’t fully clarify the exact issue you were experiencing, which made it difficult to pinpoint the cause right away.

It’s always helpful to include a screenshot or screen recording when reporting issues — this allows us to see exactly what’s happening and guide you more efficiently.

If the problem still persists after your recent changes, could you please share:

  • A screen recording or reproducible code repo

  • The Builder content link for the page in question

  • The production URL where the content is published

With these details, we can give you a more targeted solution and ensure your setup works smoothly for live editing in production.

Best regards,

Ok. But we are still talking past each other. I know what the production link is and how it is different than the content link. What I don’t understand is how to create the production link

So here is what I am trying to do. I have it working on my dev box and am trying to get it working in Azure. I have a builder.io content page that I want users (my client’s users) to be able to edit…

Note the Builder.io button in the bottom right. The user clicks the button to open the panel and then clicks another link to start the editor…

So here is the live editing along with my custom components. Great stuff! Now if I could just deploy this solution.

So I am struggling to get the Builder.io button to appear when the page is served from by Azure Web App. I can only get it to appear when I start my dev environment with “next dev”. I can’t even get it to appear with “next start” in the development environment. I know it has something to do with the builder dev tools server which listens on port 5173.

But I don’t really want the Builder.io button. All I want is the production link. How do I get that? If I could just generate and load the editor directly from my app, that would be better. To hopefully give you a better idea of what I want to do, consider my landing page editor…

This is very simple and straightforward. Here is a list of landing pages that users can edit. There are 3 actions for each page: edit, preview and delete. If the use presses the preview button the page appears rendered dynamically from builder.io content. Works great…even in production!

The edit button is what I am struggling with. The best I can do in development is to render the page with the builder.io button that enables the user to open the editor. But what I would really like to do is open the editor directly. How do I do that? If I could just generate urls like xxxhttps://builder.io/content/090ad73947934a8690a0bcb00fe3384d/edit?activeDesignerTab=0&activeLocale=Default&overridePreviewUrl=https%3A%2F%2Ffanyv88.com%3A443%2Fhttp%2Flocalhost%3A8008%2Flanding-page%2Fgeneric3 I would be happy.

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

1 Like

Thanks very much. Looking through the code you posted I finally discovered what the mysterious code was after the xxx://builder.io/content/. It is the content ID. So all I have to do to start the editor is craft a URL with the content ID and the editor is started. This is all I really wanted to do. So apparently I have to pull the page contents first so I can get the content ID and then open a window with a URL that includes the content ID. This seems obvious now. After all if you have a URL that starts xxx://builder.io/content/abc you would expect abc to be some kind of ID related to content. Somehow I never snapped to that realization. Instead I have been banging my head against this problem for over a month now. My first forum post on this problem is July 2: How to start visual editor programmatically which was never answered.

So where is the documentation on how to start the visual editor programmatically? I would expect to see a section of the documentation that reads something like “To start the visual editor programmatically create a URL in the form xxx://builder.io/content/{id} where {id} is the content id. You can get the content ID from…” Where is that?

Finally I was astounded by your post in that you are shipping code that only works in development. Who does that? I have been racking my brain to think of a product that only works in development. Diagnostic tools perhaps? No. The best debuggers work in production because the hardest problems occur only in production. The best profilers work in production because performance in production is the only thing that counts. And so on. There are no products that work only in development…except yours apparently.

So I guess I was sucked in by the marketing BS. On your website it says builder.io “gives marketers the power to generate entire on brand experiences”. I know of no marketer that has a developer environment on their machines. The only people who have developer environments and don’t write code are failed developers. So I think your website needs to be updated. It should say “builder.io gives failed developers the power to generate entire on brand experiences.”