mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-21 11:59:54 -06:00
445 lines
13 KiB
JavaScript
445 lines
13 KiB
JavaScript
import { withSentryConfig } from "@sentry/nextjs";
|
|
import createJiti from "jiti";
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const jiti = createJiti(fileURLToPath(import.meta.url));
|
|
const require = createRequire(import.meta.url);
|
|
jiti("./lib/env");
|
|
|
|
/** @type {import('next').NextConfig} */
|
|
|
|
const getHostname = (url) => {
|
|
const urlObj = new URL(url);
|
|
return urlObj.hostname;
|
|
};
|
|
|
|
const nextConfig = {
|
|
assetPrefix: process.env.ASSET_PREFIX_URL || undefined,
|
|
output: "standalone",
|
|
poweredByHeader: false,
|
|
productionBrowserSourceMaps: true,
|
|
serverExternalPackages: ["@aws-sdk", "@opentelemetry/instrumentation", "pino", "pino-pretty"],
|
|
outputFileTracingIncludes: {
|
|
"/api/auth/**/*": ["../../node_modules/jose/**/*"],
|
|
},
|
|
experimental: {},
|
|
transpilePackages: ["@formbricks/database"],
|
|
images: {
|
|
// Optimize image processing to reduce CPU time and prevent timeouts
|
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920], // Removed 3840 to avoid processing huge images
|
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Standard sizes for smaller images
|
|
formats: ["image/webp"], // WebP is faster to process and smaller than JPEG/PNG
|
|
minimumCacheTTL: 60, // Cache optimized images for at least 60 seconds
|
|
dangerouslyAllowSVG: true, // Allow SVG images
|
|
remotePatterns: [
|
|
{
|
|
protocol: "https",
|
|
hostname: "avatars.githubusercontent.com",
|
|
},
|
|
{
|
|
protocol: "https",
|
|
hostname: "avatars.slack-edge.com",
|
|
},
|
|
{
|
|
protocol: "https",
|
|
hostname: "lh3.googleusercontent.com",
|
|
},
|
|
{
|
|
protocol: "http",
|
|
hostname: "localhost",
|
|
},
|
|
{
|
|
protocol: "https",
|
|
hostname: "app.formbricks.com",
|
|
},
|
|
{
|
|
protocol: "https",
|
|
hostname: "formbricks-cdn.s3.eu-central-1.amazonaws.com",
|
|
},
|
|
{
|
|
protocol: "https",
|
|
hostname: "images.unsplash.com",
|
|
},
|
|
],
|
|
},
|
|
async redirects() {
|
|
return [
|
|
{
|
|
source: "/i/:path*",
|
|
destination: "/:path*",
|
|
permanent: false,
|
|
},
|
|
{
|
|
source: "/api/v1/surveys",
|
|
destination: "/api/v1/management/surveys",
|
|
permanent: true,
|
|
},
|
|
{
|
|
source: "/api/v1/responses",
|
|
destination: "/api/v1/management/responses",
|
|
permanent: true,
|
|
},
|
|
{
|
|
source: "/api/v1/me",
|
|
destination: "/api/v1/management/me",
|
|
permanent: true,
|
|
},
|
|
{
|
|
source: "/api/v1/me",
|
|
destination: "/api/v1/management/me",
|
|
permanent: true,
|
|
},
|
|
];
|
|
},
|
|
webpack: (config) => {
|
|
config.module.rules.push({
|
|
test: /\.(mp4|webm|ogg|swf|ogv)$/,
|
|
use: [
|
|
{
|
|
loader: "file-loader",
|
|
options: {
|
|
publicPath: "/_next/static/videos/",
|
|
outputPath: "static/videos/",
|
|
name: "[name].[hash].[ext]",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
config.resolve.fallback = {
|
|
http: false, // Prevents Next.js from trying to bundle 'http'
|
|
https: false,
|
|
};
|
|
return config;
|
|
},
|
|
async headers() {
|
|
const isProduction = process.env.NODE_ENV === "production";
|
|
const scriptSrcUnsafeEval = isProduction ? "" : " 'unsafe-eval'";
|
|
|
|
return [
|
|
{
|
|
// Apply X-Frame-Options to all routes except those starting with /s/ or /c/
|
|
source: "/((?!s/|c/).*)",
|
|
headers: [
|
|
{
|
|
key: "X-Frame-Options",
|
|
value: "SAMEORIGIN",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// matching all API routes
|
|
source: "/api/(v1|v2)/client/:path*",
|
|
headers: [
|
|
{ key: "Access-Control-Allow-Credentials", value: "true" },
|
|
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
{ key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
|
|
{
|
|
key: "Access-Control-Allow-Headers",
|
|
value:
|
|
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Cache-Control",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// matching all API routes
|
|
source: "/api/capture/:path*",
|
|
headers: [
|
|
{ key: "Access-Control-Allow-Credentials", value: "true" },
|
|
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
{ key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
|
|
{
|
|
key: "Access-Control-Allow-Headers",
|
|
value:
|
|
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Cache-Control",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: "/(.*)",
|
|
headers: [
|
|
{
|
|
key: "X-Content-Type-Options",
|
|
value: "nosniff",
|
|
},
|
|
{
|
|
key: "Content-Security-Policy",
|
|
value: `default-src 'self'; script-src 'self' 'unsafe-inline'${scriptSrcUnsafeEval} https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' blob: data: http://localhost:9000 https:; font-src 'self' data: https:; connect-src 'self' http://localhost:9000 https: wss:; frame-src 'self' https://app.cal.com https:; media-src 'self' https:; object-src 'self' data: https:; base-uri 'self'; form-action 'self'`,
|
|
},
|
|
{
|
|
key: "Strict-Transport-Security",
|
|
value: "max-age=63072000; includeSubDomains; preload",
|
|
},
|
|
{
|
|
key: "Referrer-Policy",
|
|
value: "strict-origin-when-cross-origin",
|
|
},
|
|
{
|
|
key: "Permissions-Policy",
|
|
value: "camera=(), microphone=(), geolocation=()",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: "/js/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value:
|
|
"public, max-age=3600, s-maxage=2592000, stale-while-revalidate=3600, stale-if-error=86400",
|
|
},
|
|
{
|
|
key: "Content-Type",
|
|
value: "application/javascript; charset=UTF-8",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
{
|
|
key: "Vary",
|
|
value: "Accept-Encoding",
|
|
},
|
|
],
|
|
},
|
|
// Favicon files - long cache since they rarely change
|
|
{
|
|
source: "/favicon/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=2592000, s-maxage=31536000, immutable",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
],
|
|
},
|
|
// Root favicon.ico - long cache
|
|
{
|
|
source: "/favicon.ico",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=2592000, s-maxage=31536000, immutable",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
],
|
|
},
|
|
// SVG files (icons, logos) - long cache since they're usually static
|
|
{
|
|
source: "/(.*)\\.svg",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=2592000, s-maxage=31536000, immutable",
|
|
},
|
|
{
|
|
key: "Content-Type",
|
|
value: "image/svg+xml",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
],
|
|
},
|
|
// Image backgrounds - medium cache (might update more frequently)
|
|
{
|
|
source: "/image-backgrounds/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=86400, s-maxage=2592000, stale-while-revalidate=86400",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
{
|
|
key: "Vary",
|
|
value: "Accept-Encoding",
|
|
},
|
|
],
|
|
},
|
|
// Video files - long cache since they're large and expensive to transfer
|
|
{
|
|
source: "/video/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=604800, s-maxage=31536000, stale-while-revalidate=604800",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
{
|
|
key: "Accept-Ranges",
|
|
value: "bytes",
|
|
},
|
|
],
|
|
},
|
|
// Animated backgrounds (4K videos) - very long cache since they're large and immutable
|
|
{
|
|
source: "/animated-bgs/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=604800, s-maxage=31536000, immutable",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
{
|
|
key: "Accept-Ranges",
|
|
value: "bytes",
|
|
},
|
|
],
|
|
},
|
|
// CSV templates - shorter cache since they might update with feature changes
|
|
{
|
|
source: "/sample-csv/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=3600, s-maxage=86400, stale-while-revalidate=3600",
|
|
},
|
|
{
|
|
key: "Content-Type",
|
|
value: "text/csv",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
],
|
|
},
|
|
// Web manifest and browser config files - medium cache
|
|
{
|
|
source: "/(site\\.webmanifest|browserconfig\\.xml)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=86400, s-maxage=604800, stale-while-revalidate=86400",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
],
|
|
},
|
|
// Optimize caching for other static assets in public folder (fallback)
|
|
{
|
|
source: "/(images|fonts|icons)/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=31536000, s-maxage=31536000, immutable",
|
|
},
|
|
{
|
|
key: "Access-Control-Allow-Origin",
|
|
value: "*",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
},
|
|
async rewrites() {
|
|
return [
|
|
{
|
|
source: "/api/packages/website",
|
|
destination: "/js/formbricks.umd.cjs",
|
|
},
|
|
{
|
|
source: "/api/packages/app",
|
|
destination: "/js/formbricks.umd.cjs",
|
|
},
|
|
{
|
|
source: "/api/packages/js",
|
|
destination: "/js/formbricks.umd.cjs",
|
|
},
|
|
{
|
|
source: "/api/packages/surveys",
|
|
destination: "/js/surveys.umd.cjs",
|
|
},
|
|
{
|
|
source: "/api/v1/client/:environmentId/website/environment",
|
|
destination: "/api/v1/client/:environmentId/environment",
|
|
},
|
|
{
|
|
source: "/api/v1/client/:environmentId/app/environment",
|
|
destination: "/api/v1/client/:environmentId/environment",
|
|
},
|
|
{
|
|
source: "/api/v1/client/:environmentId/app/people/:userId",
|
|
destination: "/api/v1/client/:environmentId/identify/people/:userId",
|
|
},
|
|
{
|
|
source: "/api/v1/client/:environmentId/identify/people/:userId",
|
|
destination: "/api/v1/client/:environmentId/identify/contacts/:userId",
|
|
},
|
|
{
|
|
source: "/api/v1/client/:environmentId/people/:userId/attributes",
|
|
destination: "/api/v1/client/:environmentId/contacts/:userId/attributes",
|
|
},
|
|
{
|
|
source: "/api/v1/management/people/:id*",
|
|
destination: "/api/v1/management/contacts/:id*",
|
|
},
|
|
{
|
|
source: "/api/v1/management/attribute-classes",
|
|
destination: "/api/v1/management/contact-attribute-keys",
|
|
},
|
|
{
|
|
source: "/api/v1/management/attribute-classes/:id*",
|
|
destination: "/api/v1/management/contact-attribute-keys/:id*",
|
|
},
|
|
];
|
|
},
|
|
env: {
|
|
NEXTAUTH_URL: process.env.WEBAPP_URL,
|
|
},
|
|
};
|
|
|
|
// set actions allowed origins
|
|
if (process.env.WEBAPP_URL) {
|
|
nextConfig.experimental.serverActions = {
|
|
allowedOrigins: [process.env.WEBAPP_URL.replace(/https?:\/\//, "")],
|
|
bodySizeLimit: "2mb",
|
|
};
|
|
}
|
|
|
|
// Allow all origins for next/image
|
|
nextConfig.images.remotePatterns.push({
|
|
protocol: "https",
|
|
hostname: "**",
|
|
});
|
|
|
|
const sentryOptions = {
|
|
// For all available options, see:
|
|
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
|
|
project: "formbricks-cloud",
|
|
org: "formbricks",
|
|
|
|
// Enable logging to debug sourcemap generation issues
|
|
silent: false,
|
|
|
|
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
|
widenClientFileUpload: true,
|
|
|
|
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
|
disableLogger: false,
|
|
};
|
|
|
|
// Always enable Sentry plugin to inject Debug IDs
|
|
// Runtime Sentry reporting still depends on DSN being set via environment variables
|
|
const exportConfig = process.env.SENTRY_AUTH_TOKEN ? withSentryConfig(nextConfig, sentryOptions) : nextConfig;
|
|
|
|
export default exportConfig;
|