M

The Right Way to Proxy Self-Hosted OpenPanel in Next.js

Published: Dec 20, 2025

If you're self-hosting OpenPanel on Coolify, you've likely encountered a common headache: managing multiple subdomains. Coolify's one-click deploy for OpenPanel creates separate services - the Dashboard/CDN on one domain (e.g., dashboard.yourdomain.com) and the API on another (api.yourdomain.com).

When you try to integrate this into Next.js, things get messy:

  1. CORS Errors: "Invalid cors or secret" errors pop up because your app domain doesn't match the API domain.
  2. Ad-Blockers: Scripts loading from third-party domains get blocked.
  3. Official Docs Limitations: The official Next.js SDK recommends using Route Handlers (createRouteHandler). While this works for SaaS, it can struggle with self-hosted setups where you need to proxy both the script (op1.js) and the API events across different upstream domains.

The verified solution? Ignore the Route Handler advice and use Next.js Rewrites.

The Problem with Route Handlers

When you use the standard Route Handler approach with a self-hosted instance, you often hit authentication walls. The backend expects specific headers (openpanel-client-secret) for server-side ingestion that client-side requests can't safely provide. Plus, mapping a single /api/op route to handle both script delivery (from the Dashboard domain) and event tracking (to the API domain) requires custom logic.

Rewrites solve this elegantly by letting Next.js handle the proxying at the network layer, keeping the request context intact.

The Fix: Next.js Rewrites

We'll map two local paths to our two separate self-hosted services.

next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  // ... other config
  async rewrites() {
    return [
      {
       // Proxy the script from your Dashboard domain
       source: "/op1.js",
       destination: "https://opdashboard.yourdomain.com/op1.js",
      },
      {
       // Proxy events to your API domain
       source: "/api/op/:path*",
       destination: "https://opapi.yourdomain.com/:path*",
      },
    ];
  },
};
 
export default nextConfig;

Replace the destination URLs with your actual self-hosted domains.

Configure the Client

Now, tell the OpenPanel SDK to use these "local" paths. It will treat your app as the source of truth, avoiding cross-origin complexities.

src/app/layout.tsx
import { OpenPanelComponent } from "@openpanel/nextjs";
 
// ...
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* ... other components */}
        
        {process.env.NODE_ENV === "production" && (
          <OpenPanelComponent
            clientId={process.env.OPENPANEL_CLIENT_ID}
            apiUrl="/api/op" 
            cdnUrl="/op1.js" 
            
            trackScreenViews={true}
            trackOutgoingLinks={true}
            trackAttributes={true}
          />
        )}
      </body>
    </html>
  );
}

Why This is Better for Self-Hosting

  1. Unified Origin: Your app sees everything as same-origin (/api/op).
  2. Ad-Blocker Resistant: Scripts load from your domain, not a third-party analytics domain.
  3. No Secret Exposure: Unlike Route Handlers, you don't need to handle openpanel-client-secret on the client side.
  4. Zero Custom Code: Rewrites work at the network layer - no middleware or API routes to maintain.

Conclusion

For self-hosted OpenPanel on Coolify (or any setup with separate Dashboard and API domains), Next.js Rewrites are the cleanest solution. They handle the multi-domain proxy transparently while keeping your client configuration simple.


For more on the official SDK, check out the OpenPanel Next.js documentation.