Issues with Accessing Dynamic Routes with Clerk Authentication and Builder.io

What are you trying to accomplish
I’m encountering an issue with accessing dynamic routes in my Next.js application that uses Clerk for authentication, and the routes are managed by Builder.io. Specifically, I’m getting a 401 Unauthorized response when trying to access dynamic routes such as /studio/variis. I need to ensure that these dynamic routes are correctly recognized as public or private by Clerk middleware and can be accessed without authentication issues.

Code stack you are integrating Builder with
Next.js, Clerk for authentication, Builder.io for content management

Reproducible code example
Here is the code for my current middleware setup, which is intended to manage authentication and handle dynamic routes. Despite the configuration, I’m receiving 401 Unauthorized errors for certain routes.

import { authMiddleware, redirectToSignIn } from '@clerk/nextjs'
import { NextRequest, NextResponse } from 'next/server'

export default authMiddleware({
  publicRoutes: [
    '/api(.*)', 
    '/studio(.*)', 
    '/v2(.*)', 
    '/sanity-studio(.*)', 
    '/(.*)' // General catch-all for dynamic routes
  ],
  afterAuth(auth, req) {
    const pathname = req.nextUrl.pathname;

    // Debug logging
    console.log('Request URL:', req.url);
    console.log('Is public route:', auth.isPublicRoute);
    console.log('User ID:', auth.userId);
    console.log('Authorization header:', req.headers.get('authorization'));

    // Handle public routes
    if (auth.isPublicRoute || pathname.startsWith('/studio') || pathname.startsWith('/variis')) {
      return NextResponse.next();
    }

    // Redirect to sign-in if not authenticated
    if (!auth.userId) {
      return redirectToSignIn({ returnBackUrl: req.url });
    }

    // Handle API authentication
    if (pathname.startsWith('/api') && !req.headers.get('authorization')) {
      if (req.headers.get('authorization') !== `Bearer ${process.env.API_KEY}`) {
        return new NextResponse(
          JSON.stringify({ success: false, message: 'Authentication failed' }),
          { status: 401, headers: { 'Content-Type': 'application/json' } }
        );
      }
    }

    // Allow other authenticated requests to proceed
    return NextResponse.next();
  },
});

export const config = {
  matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)', '/studio(.*)',],
}

I would appreciate any guidance on ensuring that dynamic routes managed by Builder.io are correctly handled by Clerk authentication. Specifically, I need to understand why these routes are being flagged as unauthorized and how to adjust my configuration to resolve this issue.

Hello @amanuel.tadesse,

To address the issue of dynamic routes receiving a 401 Unauthorized response when using Clerk for authentication and Builder.io for content management, I’ll provide guidance on ensuring that your middleware correctly handles these routes.

First, ensure your authMiddleware is correctly recognizing the public routes and handling authentication for private routes:

  1. Define Public Routes Accurately: Ensure that all possible dynamic routes that should be accessible publicly are included in the publicRoutes array.
  2. Handle Authorization for Private Routes: Ensure that afterAuth correctly distinguishes between public and private routes using specified authorization logic.

Here’s a refined middleware setup based on your current configuration:

import { authMiddleware, redirectToSignIn } from '@clerk/nextjs'
import { NextResponse } from 'next/server'

export default authMiddleware({
  publicRoutes: [
    '/api(.*)',  // API routes
    '/studio(.*)',  // Studio routes
    '/v2(.*)',  // Additional public routes
    '/sanity-studio(.*)',
    /(?!^\/api\/(?!public).*$).*$/ // Public fallback for dynamic routes
  ],
  afterAuth(auth, req) {
    const pathname = req.nextUrl.pathname;

    // Debug logging
    console.log('Request URL:', req.url);
    console.log('Is public route:', auth.isPublicRoute);
    console.log('User ID:', auth.userId);
    console.log('Authorization header:', req.headers.get('authorization'));

    // Handle authorized requests
    if (auth.isPublicRoute || pathname.startsWith('/studio') || pathname.startsWith('/variis')) {
      return NextResponse.next();
    }

    // Redirect to sign-in if not authenticated
    if (!auth.userId) {
      return redirectToSignIn({ returnTo: req.url });
    }

    // Handle API authentication
    if (pathname.startsWith('/api') && !req.headers.get('authorization')) {
      if (req.headers.get('authorization') !== `Bearer ${process.env.API_KEY}`) {
        return new NextResponse(
          JSON.stringify({ success: false, message: 'Authentication failed' }),
          { status: 401, headers: { 'Content-Type': 'application/json' } }
        );
      }
    }

    // Allow authenticated requests to proceed
    return NextResponse.next();
  },
});

export const config = {
  matcher: [
    '/((?!.+\\.[\\w]+$|_next).*)',  // Match all routes except static files and next.js internals
    '/', '/(api|trpc)(.*)', '/studio(.*)',
  ],
}

Handling Builder.io Dynamic Routes

Ensure you have the proper configuration for dynamic routes in Builder.io. Builder.io can manage dynamic routes easily with Next.js. For integrated routes such as /studio/variis, set up your pages directory within Next.js appropriately:

  1. Dynamic Routing Setup:

Ensure your dynamic routes are correctly set up in Next.js. For instance, if you have [...page].tsx handling all dynamic routes:

// pages/[...page].tsx
import { builder, BuilderComponent } from '@builder.io/react'
import { useRouter } from 'next/router'
import DefaultErrorPage from 'next/error'
import Head from 'next/head'

builder.init('YOUR_API_KEY')

export async function getStaticProps({ params }) {
  const page = await builder
    .get('page', {
      userAttributes: { urlPath: '/' + (params?.page?.join('/') || '') }
    })
    .toPromise() || null

  return {
    props: { page },
    revalidate: 5
  }
}

export async function getStaticPaths() {
  const pages = await builder.getAll('page', { options: { noTargeting: true } })

  return { paths: pages.map(page => `${page.data?.url}`), fallback: true }
}

export default function Page({ page }) {
  const router = useRouter()

  if (router.isFallback) {
    return <h1>Loading...</h1>
  }

  if (!page) {
    return <DefaultErrorPage statusCode={404} />
  }

  return (
    <>
      <Head>
        <title>{page?.data.title}</title>
      </Head>
      <BuilderComponent model="page" content={page} />
    </>
  )
}

Hope this helps! Let us know if the issue still persists.

Thanks,

@manish-sharma thanks for the help
with the code you provided i was not able to get it authenticated one thing to notice is this is happening only on the builder studio preview. if i view it using the preview draft or preview published page it work’s fine with out any issue i also enabled Reload preview on URL path change and
Proxy previews
FYI: am using NEXTJS app router

Hello @amanuel.tadesse,

Can you confirm if the builder preview is not working when using localhost or if the app is deployed? Additionally, please check if there are any restrictions for loading the preview in the builder iFrame i.e. CORS or X-Frame-Options headers?

this is the error on my console


the weired thing i noticed is when i just copy the url from the network and past it on my browser or send it as a get request via post man it work’s with out any issue

Hello @amanuel.tadesse,

Do your pages require authentication to access? We suspect this could be due to an authentication layer. Additionally, can you verify the same-site header from the network request when in the builder preview and see if it’s set to Same-Site: lax?