/* * Copyright (c) 2026 Eliott Guillaumin * All rights reserved. */ import path from "node:path" import { fileURLToPath } from "node:url" /** @type {import('next').NextConfig} */ const projectRoot = path.dirname(fileURLToPath(import.meta.url)) const ultiProxyOrigin = process.env.ULTI_PROXY_ORIGIN ?? "http://127.0.0.1" /** "s" for https/wss when SECURE=s (or true/1). */ function suiteSecureSuffix() { const secure = process.env.SECURE?.trim().toLowerCase() if (secure === "s" || secure === "true" || secure === "1") return "s" return "" } /** Derive browser-facing NEXT_PUBLIC_* URLs from PUBLIC_HOST + SECURE. */ function suitePublicEnv() { const host = ( process.env.PUBLIC_HOST ?? process.env.DOMAIN ?? "localhost" ).trim() const s = suiteSecureSuffix() const origin = `http${s}://${host}` return { NEXT_PUBLIC_APP_URL: origin, NEXT_PUBLIC_WS_URL: `ws${s}://${host}/ws`, NEXT_PUBLIC_OIDC_ISSUER: `${origin}/auth/application/o/ulti/`, NEXT_PUBLIC_ONLYOFFICE_URL: `${origin}/office`, NEXT_PUBLIC_HOCUSPOCUS_URL: `ws${s}://${host}/collab/`, NEXT_PUBLIC_AI_ORIGIN: origin, NEXT_PUBLIC_ULTISPACE_ORIGIN: origin, } } /** Hostnames allowed to load /_next/* in dev (tunnel, LAN, public dev domain). */ function allowedDevOrigins() { const hosts = new Set(["127.0.0.1", "localhost"]) const publicHost = ( process.env.PUBLIC_HOST ?? process.env.DOMAIN ?? "" ).trim() if (publicHost) hosts.add(publicHost) const derived = suitePublicEnv().NEXT_PUBLIC_APP_URL if (derived) { try { hosts.add(new URL(derived).hostname) } catch { /* ignore invalid origin */ } } return [...hosts] } /** * Mobile (Tauri) build: a fully static export with no server runtime. The * server-only API route handlers are named `route.web.ts` and are excluded * here by NOT registering the `web.ts` page extension (see `pageExtensions`), * so they only compile for the web/standalone build. */ const isMobile = process.env.NEXT_PUBLIC_MOBILE === "1" const webPageExtensions = ["web.tsx", "web.ts", "tsx", "ts", "jsx", "js"] const mobilePageExtensions = ["tsx", "ts", "jsx", "js"] /** @type {import('next').NextConfig} */ const baseConfig = { outputFileTracingRoot: projectRoot, allowedDevOrigins: allowedDevOrigins(), env: suitePublicEnv(), typescript: { ignoreBuildErrors: true, }, images: { unoptimized: true, }, turbopack: { resolveAlias: { canvas: "./lib/empty-module.mjs", }, }, webpack: (config) => { config.resolve.alias = { ...config.resolve.alias, canvas: false, } return config }, } const webConfig = { ...baseConfig, output: "standalone", // Docker standalone tracing only — do not set turbopack.root to projectRoot: // breaks Tailwind @import resolution (resolves from parent /Users/red/workdev). pageExtensions: webPageExtensions, async redirects() { return [ { source: "/mail/settings", destination: "/settings", permanent: true, }, { source: "/mail/settings/:section*", destination: "/settings/:section*", permanent: true, }, { source: "/compte", destination: "/account", permanent: true, }, { source: "/compte/:section*", destination: "/account/:section*", permanent: true, }, ] }, async rewrites() { return [ { source: "/api/v1/:path*", destination: `${ultiProxyOrigin}/api/v1/:path*`, }, { source: "/ai/:path*", destination: `${ultiProxyOrigin}/ai/:path*`, }, { source: "/ai", destination: `${ultiProxyOrigin}/ai`, }, ] }, } const mobileConfig = { ...baseConfig, output: "export", // Mobile shells navigate client-side; the static export serves the start // route (e.g. /mail) and Next's router + native deep-links handle the rest. trailingSlash: true, pageExtensions: mobilePageExtensions, // No rewrites/redirects: the bundle calls an absolute backend URL chosen at // runtime by the server picker, and path redirects run client-side. } const nextConfig = isMobile ? mobileConfig : webConfig export default nextConfig