diff --git a/Dockerfile b/Dockerfile index 0a29511..60ae4a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,7 +75,7 @@ WORKDIR /app # Copy standalone output and static files (Monorepo paths) # Note: Base image already handles the non-root user and basic env -COPY --from=builder --chown=1001:65533 /app/apps/web/public ./apps/web/public +COPY --from=builder --chown=1001:65533 /app/apps/web/public ./public COPY --from=builder --chown=1001:65533 /app/apps/web/.next/standalone ./ COPY --from=builder --chown=1001:65533 /app/apps/web/.next/static ./apps/web/.next/static diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index a7782bb..8637c1b 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -46,57 +46,12 @@ const nextConfig = { }, ]; }, + // In Standalone mode, Next.js expects the tracing root to be the monorepo root outputFileTracingRoot: path.join(dirname, '../../'), }; const withMDX = createMDX({}); -// Execute wrappers -let config = withPayload(withMintelConfig(withMDX(nextConfig))); - -/** - * Injects our critical showcase rewrites into a resolved Next.js config object. - */ -function injectShowcaseRewrites(resolvedConfig) { - const originalRewrites = resolvedConfig.rewrites; - - resolvedConfig.rewrites = async () => { - const existing = await originalRewrites?.() || []; - - // Normalize existing to object pattern - const normalized = Array.isArray(existing) - ? { afterFiles: existing, beforeFiles: [], fallback: [] } - : existing; - - return { - ...normalized, - beforeFiles: [ - ...(normalized.beforeFiles || []), - // Diagnostic rule - { source: '/robots-test', destination: '/robots.txt' }, - // Showcase Assets - { source: '/assets/:path*', destination: '/showcase/klz-cables.com/assets/:path*' }, - { source: '/wp-content/:path*', destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-content/:path*' }, - { source: '/wp-includes/:path*', destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-includes/:path*' }, - { source: '/:folder(case-studies|work|blog)/assets/:path*', destination: '/showcase/klz-cables.com/assets/:path*' }, - { source: '/:folder(case-studies|work|blog)/wp-content/:path*', destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-content/:path*' }, - { source: '/:folder(case-studies|work|blog)/wp-includes/:path*', destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-includes/:path*' }, - ], - }; - }; - - return resolvedConfig; -} - -// Handle both standard objects and functional wrappers (like withPayload) -if (typeof config === 'function') { - const originalConfigWrapper = config; - config = async (phase, args) => { - const resolved = await originalConfigWrapper(phase, args); - return injectShowcaseRewrites(resolved); - }; -} else { - config = injectShowcaseRewrites(config); -} - -export default config; +// Clean, standard wrapper application +// Rewrites are now handled by src/middleware.ts for maximum robustness +export default withPayload(withMintelConfig(withMDX(nextConfig))); diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts new file mode 100644 index 0000000..9a3e722 --- /dev/null +++ b/apps/web/src/middleware.ts @@ -0,0 +1,53 @@ +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +/** + * PRODUCTION STABILIZATION MIDDLEWARE + * This middleware handles legacy asset routing for the KLZ showcase, + * ensuring assets are correctly mapped regardless of Next.js config wrapper behavior. + */ +export function middleware(request: NextRequest) { + const { pathname } = request.nextUrl; + + // 1. Diagnostic Rewrite + if (pathname === '/robots-test') { + return NextResponse.rewrite(new URL('/robots.txt', request.url)); + } + + // 2. Legacy Showcase Asset Mapping + // Matches: /wp-content/*, /wp-includes/*, /assets/*, and their relative subpaths + const showcaseAssetPattern = /^\/(?:wp-content|wp-includes|assets|(?:case-studies|work|blog)\/(?:wp-content|wp-includes|assets))\/(.*)/; + const match = pathname.match(showcaseAssetPattern); + + if (match) { + const assetPath = match[0]; + + // Normalize path by stripping the case-studies/work/blog prefix if present + const normalizedPath = assetPath.replace(/^\/(?:case-studies|work|blog)/, ''); + + // Map to the literal directory in public/ + // Destination: /showcase/klz-cables.com/... + return NextResponse.rewrite(new URL(`/showcase/klz-cables.com${normalizedPath}`, request.url)); + } + + return NextResponse.next(); +} + +// Optimization: Only run middleware for legacy asset paths to minimize overhead +export const config = { + matcher: [ + '/robots-test', + '/wp-content/:path*', + '/wp-includes/:path*', + '/assets/:path*', + '/case-studies/wp-content/:path*', + '/case-studies/wp-includes/:path*', + '/case-studies/assets/:path*', + '/work/wp-content/:path*', + '/work/wp-includes/:path*', + '/work/assets/:path*', + '/blog/wp-content/:path*', + '/blog/wp-includes/:path*', + '/blog/assets/:path*', + ], +};