Files
dataroom/next.config.mjs
zeekayandHanzo Dev 8b91151096 Stop allowlisting Papermark's domains for image fetching
next/image will only fetch and re-serve a remote host named in
remotePatterns, and six of ours were upstream's: assets.papermark.io,
cdn.papermarkassets.com, and the www/app pairs on papermark.io and
papermark.com. Nothing in the tree builds a URL on any of them — the only
mentions were these six lines — so this removes reach we were never using
into a domain we do not control.

The env-driven NEXT_PRIVATE_*_DISTRIBUTION_HOST entries are how a deployment
names its own storage, and the twitter/linkedin/google/useragent hosts are
avatars from the providers we authenticate against. Both stay.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 18:58:32 -07:00

259 lines
7.1 KiB
JavaScript

/** @type {import('next').NextConfig} */
const nextConfig = {
output: process.env.DOCKER_OUTPUT ? "standalone" : undefined,
reactStrictMode: true,
pageExtensions: ["js", "jsx", "ts", "tsx", "mdx"],
images: {
minimumCacheTTL: 2592000, // 30 days
remotePatterns: prepareRemotePatterns(),
},
skipTrailingSlashRedirect: true,
assetPrefix:
process.env.NODE_ENV === "production" &&
process.env.VERCEL_ENV === "production"
? process.env.NEXT_PUBLIC_BASE_URL
: undefined,
async redirects() {
return [
{
source: "/",
destination: "/dashboard",
permanent: false,
has: [
{
type: "host",
value: process.env.NEXT_PUBLIC_APP_BASE_HOST || "dataroom.hanzo.ai",
},
],
},
{
// temporary redirect set on 2025-10-22
source: "/view/cmdn06aw00001ju04jgsf8h4f",
destination: "/view/cmh0uiv6t001mjm04sk10ecc8",
permanent: false,
},
{
source: "/settings",
destination: "/settings/general",
permanent: false,
},
];
},
async headers() {
const isDev = process.env.NODE_ENV === "development";
return [
{
// Default headers for all routes
source: "/:path*",
headers: [
{
key: "Referrer-Policy",
value: "no-referrer-when-downgrade",
},
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "Report-To",
value: JSON.stringify({
group: "csp-endpoint",
max_age: 10886400,
endpoints: [{ url: "/api/csp-report" }],
}),
},
{
key: "Content-Security-Policy-Report-Only",
value:
`default-src 'self' https: ${isDev ? "http:" : ""}; ` +
`script-src 'self' 'unsafe-inline' 'unsafe-eval' https: ${isDev ? "http:" : ""}; ` +
`style-src 'self' 'unsafe-inline' https: ${isDev ? "http:" : ""}; ` +
`img-src 'self' data: blob: https: ${isDev ? "http:" : ""}; ` +
`font-src 'self' data: https: ${isDev ? "http:" : ""}; ` +
`frame-ancestors 'none'; ` +
`connect-src 'self' https: ${isDev ? "http: ws: wss:" : ""}; ` + // Add WebSocket for hot reload
`${isDev ? "" : "upgrade-insecure-requests;"} ` +
"report-to csp-endpoint;",
},
],
},
{
source: "/view/:path*",
headers: [
{
key: "X-Robots-Tag",
value: "noindex",
},
],
},
{
source: "/login",
has: [
{
type: "query",
key: "next",
},
],
headers: [
{
key: "X-Robots-Tag",
value: "noindex, nofollow",
},
],
},
{
// Embed routes - allow iframe embedding
source: "/view/:path*/embed",
headers: [
{
key: "Content-Security-Policy",
value:
`default-src 'self' https: ${isDev ? "http:" : ""}; ` +
`script-src 'self' 'unsafe-inline' 'unsafe-eval' https: ${isDev ? "http:" : ""}; ` +
`style-src 'self' 'unsafe-inline' https: ${isDev ? "http:" : ""}; ` +
`img-src 'self' data: blob: https: ${isDev ? "http:" : ""}; ` +
`font-src 'self' data: https: ${isDev ? "http:" : ""}; ` +
"frame-ancestors *; " + // This allows iframe embedding
`connect-src 'self' https: ${isDev ? "http: ws: wss:" : ""}; ` + // Add WebSocket for hot reload
`${isDev ? "" : "upgrade-insecure-requests;"}`,
},
{
key: "X-Robots-Tag",
value: "noindex",
},
],
},
{
source: "/services/:path*",
has: [
{
type: "host",
value:
process.env.NEXT_PUBLIC_WEBHOOK_BASE_HOST || "dataroom.hanzo.ai",
},
],
headers: [
{
key: "X-Robots-Tag",
value: "noindex",
},
],
},
{
source: "/api/webhooks/services/:path*",
headers: [
{
key: "X-Robots-Tag",
value: "noindex",
},
],
},
{
source: "/unsubscribe",
headers: [
{
key: "X-Robots-Tag",
value: "noindex",
},
],
},
];
},
typescript: {
ignoreBuildErrors: true,
},
experimental: {
outputFileTracingIncludes: {
"/api/mupdf/*": ["./node_modules/mupdf/dist/*.wasm"],
},
missingSuspenseWithCSRBailout: false,
},
webpack: (config) => {
// Stub out @google-cloud/kms - it's an optional dependency of @libpdf/core
// that we don't use (only needed for KMS-based PDF encryption)
config.resolve.alias = {
...config.resolve.alias,
"@google-cloud/kms": false,
"@google-cloud/secret-manager": false,
};
return config;
},
};
function prepareRemotePatterns() {
let patterns = [
{ protocol: "https", hostname: "d2kgph70pw5d9n.cloudfront.net" },
// twitter img
{ protocol: "https", hostname: "pbs.twimg.com" },
// linkedin img
{ protocol: "https", hostname: "media.licdn.com" },
// google img
{ protocol: "https", hostname: "lh3.googleusercontent.com" },
// useragent img
{ protocol: "https", hostname: "faisalman.github.io" },
// special document pages
{ protocol: "https", hostname: "d36r2enbzam0iu.cloudfront.net" },
// us special storage
{ protocol: "https", hostname: "d35vw2hoyyl88.cloudfront.net" },
];
// Default region patterns
if (process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_HOST) {
patterns.push({
protocol: "https",
hostname: process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_HOST,
});
}
if (process.env.NEXT_PRIVATE_ADVANCED_UPLOAD_DISTRIBUTION_HOST) {
patterns.push({
protocol: "https",
hostname: process.env.NEXT_PRIVATE_ADVANCED_UPLOAD_DISTRIBUTION_HOST,
});
}
// US region patterns
if (process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_HOST_US) {
patterns.push({
protocol: "https",
hostname: process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_HOST_US,
});
}
if (process.env.NEXT_PRIVATE_ADVANCED_UPLOAD_DISTRIBUTION_HOST_US) {
patterns.push({
protocol: "https",
hostname: process.env.NEXT_PRIVATE_ADVANCED_UPLOAD_DISTRIBUTION_HOST_US,
});
}
if (process.env.VERCEL_ENV === "production") {
patterns.push({
// production vercel blob
protocol: "https",
hostname: "yoywvlh29jppecbh.public.blob.vercel-storage.com",
});
}
if (
process.env.VERCEL_ENV === "preview" ||
process.env.NODE_ENV === "development"
) {
patterns.push({
// staging vercel blob
protocol: "https",
hostname: "36so9a8uzykxknsu.public.blob.vercel-storage.com",
});
}
return patterns;
}
export default nextConfig;