Next.js Discord

Discord Forum

Nexjs14 App Router : Tailwind styles not applied in Multi-tenant subdomains

Unanswered
Weevil parasitoid posted this in #help-forum
Open in Discord
Avatar
Weevil parasitoidOP
theres an issue i cant seem to figure out in fixing it .. this issue is happening production(vercel) ... the project im working on is a a multi tenant page builder project , where users can build their own website using the platform i built using nextjs 14 App router with Grapesjs.. the main issue i have right now ....is when i access the created website through the platform.. eg: heavy.web.page ... everything shows but it looks not styled everything is out of place ... when i inspect the requests i get 404 errors .. here is the code that im working with below:
Image

6 Replies

Avatar
Weevil parasitoidOP
 // app/[domain]/page.tsx
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { prisma } from "@/lib/prisma";
import { getProjectMetadata } from '@/components/domain/metadata';
import { Circle } from 'lucide-react';
import { sanitizeCSS, sanitizeHTML } from '@/lib/serverDOMPurify';
//import parse from 'html-react-parser';


export const dynamic = 'force-dynamic'
//export const revalidate = 60; // Revalidate every minute

interface PageProps {
  params: {
    domain: string;
  };
}

// Define a type for the project data
interface ProjectData {
  pagesData?: Array<{
    html: string;
    css: string;
    name?: string;
    metadata?: Record<string, string>;
  }>;
}

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  return getProjectMetadata(params.domain);
}



export default async function DomainPage({ params }: PageProps) {
  const { domain } = params;
 
  // Use Prisma's JSON type and specify the expected structure
  const project = await prisma.project.findFirst({
    where: {
      OR: [
        { subdomain: domain },
        { customDomain: domain }
      ]
    },
    select: {
      data: true,
      isPublished: true
    }
  });

 
  // Safely parse the project data
  let projectData: ProjectData | null = null;
  try {
    projectData = project.data as ProjectData;
  } catch (error) {
    return notFound();
  }

  // Get the first page (home page)
  const mainPage = projectData?.pagesData?.[0];
  if (!mainPage) {
    return notFound();
  }

  // Sanitize HTML and CSS
  const sanitizedHTML = sanitizeHTML(mainPage.html || '');
  const sanitizedCSS = sanitizeCSS(mainPage.css || '');

  // Server-side rendering of HTML and CSS
  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: sanitizedCSS }} />
      <div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
    </>
  );
} 
 // app/[domain]/layout.tsx
import React from "react";
import { Metadata } from 'next';
import { getProjectMetadata } from '@/components/domain/metadata';

export const dynamic = 'force-dynamic';

interface LayoutProps {
  children: React.ReactNode
  params: { domain: string }
}

export async function generateMetadata({ 
  params 
}: { 
  params: { domain: string } 
}): Promise<Metadata> { 
  return getProjectMetadata(params.domain)
}

export default async function DomainLayout({ 
  children, 
  params 
}: LayoutProps) {
  return (
    <div>
      {children}
    </div>
  )
} 
 /** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return {
      beforeFiles: [
        {
          source: '/:path*',
          
          has: [
            {
              type: 'host',
              value: '(?<domain>.*)\\.web\\.page'
            }
          ],
          destination: '/:domain/:path*'
        }
      ]
    };
  },
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'avatars.githubusercontent.com',
      },
      {
        protocol: 'https',
        hostname: 'lh3.googleusercontent.com',
      },
      {
        protocol: 'https',
        hostname: 'dummyimage.com',
      },
    ],
  },
};

export default nextConfig; 
 // middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export const auth = NextAuth(authConfig).auth;

export const config = {
  matcher: [
    "/((?!api|_next/static|_next/image|favicon.ico).*)",
  ],
};

export default async function middleware(req: NextRequest) {
  try {
    const hostname = req.headers.get("host") || 'NO_HOST_FOUND';
    const path = req.nextUrl.pathname;
    const searchParams = req.nextUrl.searchParams.toString();

    // Normalize hostname
    const normalizedHostname = hostname.replace(/^www\./, '');
    
    // Check if it's a subdomain
    const hostParts = normalizedHostname.split('.');
    const isSubdomain = 
      hostParts.length > 2 && 
      hostParts[0] !== 'www' && 
      normalizedHostname !== process.env.NEXT_PUBLIC_ROOT_DOMAIN;

    // Extract subdomain
    const subdomain = isSubdomain ? hostParts[0] : null;

    // Construct full path with search params
    const fullPath = `${path}${searchParams.length > 0 ? `?${searchParams}` : ""}`;

    // Subdomain routing
    if (subdomain) {
      console.log('Subdomain detected:', subdomain);
      
      // Rewrite to subdomain-specific route
      const rewriteUrl = new URL(`/${subdomain}${fullPath}`, req.url);
      
      console.log('Rewriting to:', rewriteUrl.toString());
      return NextResponse.rewrite(rewriteUrl);
    }

    // Default routing
    return NextResponse.rewrite(new URL(fullPath, req.url));
  } catch (error) {
    console.error('Middleware Error:', error);
    return NextResponse.redirect(new URL('/', req.url));
  }
}
 
Avatar
@Weevil parasitoid // middleware.ts import { NextRequest, NextResponse } from 'next/server'; import NextAuth from 'next-auth'; import { authConfig } from './auth.config'; export const auth = NextAuth(authConfig).auth; export const config = { matcher: [ "/((?!api|_next/static|_next/image|favicon.ico).*)", ], }; export default async function middleware(req: NextRequest) { try { const hostname = req.headers.get("host") || 'NO_HOST_FOUND'; const path = req.nextUrl.pathname; const searchParams = req.nextUrl.searchParams.toString(); // Normalize hostname const normalizedHostname = hostname.replace(/^www\./, ''); // Check if it's a subdomain const hostParts = normalizedHostname.split('.'); const isSubdomain = hostParts.length > 2 && hostParts[0] !== 'www' && normalizedHostname !== process.env.NEXT_PUBLIC_ROOT_DOMAIN; // Extract subdomain const subdomain = isSubdomain ? hostParts[0] : null; // Construct full path with search params const fullPath = `${path}${searchParams.length > 0 ? `?${searchParams}` : ""}`; // Subdomain routing if (subdomain) { console.log('Subdomain detected:', subdomain); // Rewrite to subdomain-specific route const rewriteUrl = new URL(`/${subdomain}${fullPath}`, req.url); console.log('Rewriting to:', rewriteUrl.toString()); return NextResponse.rewrite(rewriteUrl); } // Default routing return NextResponse.rewrite(new URL(fullPath, req.url)); } catch (error) { console.error('Middleware Error:', error); return NextResponse.redirect(new URL('/', req.url)); } }
Avatar
Check if the incoming pathname starts with _next

If yes, simply continue (NextResponse.next)

If no, rewrite as usual
Avatar
@joulev Check if the incoming pathname starts with _next If yes, simply continue (NextResponse.next) If no, rewrite as usual
Avatar
Weevil parasitoidOP
did that just now , the issue is persisting