Files
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

475 lines
14 KiB
TypeScript

import path from 'node:path'
import type { NextConfig } from 'next'
import { env, isTruthy } from './lib/core/config/env'
import { isDev } from './lib/core/config/env-flags'
import {
getChatEmbedCSPPolicy,
getMainCSPPolicy,
getWorkflowExecutionCSPPolicy,
} from './lib/core/security/csp'
/**
* Dev-only escape hatch: when `SIM_DEV_MINIMAL_REGISTRY=1` (`bun run dev:minimal`),
* swap the heavy block and tool registries for tiny curated variants via a
* Turbopack/webpack resolve alias. The shared workspace layout drags the
* ~247-tool registry (~2,074 modules) into every route via providers/utils →
* tools/params, and the editor/executor pull all ~268 block configs; aliasing
* both to minimal variants stops Turbopack from compiling those graphs, cutting
* dev compile-time RAM (e.g. /logs ~16GB → ~5GB, 4.9min → ~15s). Only the
* curated core blocks/tools work in this mode. Never enabled in production.
*/
const useMinimalRegistry = isDev && process.env.SIM_DEV_MINIMAL_REGISTRY === '1'
const minimalRegistryAlias: Record<string, string> = useMinimalRegistry
? {
'@/tools/registry': './tools/registry.minimal.ts',
'@/blocks/registry-maps': './blocks/registry-maps.minimal.ts',
}
: {}
const nextConfig: NextConfig = {
devIndicators: false,
poweredByHeader: false,
// Safe here since this repo's source is already fully public on GitHub -
// no additional exposure versus Next's default (disabled to avoid leaking
// source on the client).
productionBrowserSourceMaps: true,
turbopack: {
root: path.join(import.meta.dirname, '../..'),
resolveAlias: minimalRegistryAlias,
},
webpack: (config) => {
if (useMinimalRegistry) {
config.resolve.alias = {
...config.resolve.alias,
'@/tools/registry$': path.resolve(import.meta.dirname, 'tools/registry.minimal.ts'),
'@/blocks/registry-maps$': path.resolve(
import.meta.dirname,
'blocks/registry-maps.minimal.ts'
),
}
}
return config
},
images: {
formats: ['image/avif', 'image/webp'],
/**
* Allowed `quality` values for next/image. 75 is the app-wide default;
* 90 exists for large photographic marketing assets (the landing hero
* backdrop) where the default visibly softens texture.
*/
qualities: [75, 90],
remotePatterns: [
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com',
},
{
protocol: 'https',
hostname: 'api.stability.ai',
},
// Azure Blob Storage
{
protocol: 'https',
hostname: '*.blob.core.windows.net',
},
// AWS S3
{
protocol: 'https',
hostname: '*.s3.amazonaws.com',
},
{
protocol: 'https',
hostname: '*.s3.*.amazonaws.com',
},
{
protocol: 'https',
hostname: 'lh3.googleusercontent.com',
},
// Brand logo domain if configured
...(process.env.NEXT_PUBLIC_BRAND_LOGO_URL
? (() => {
try {
return [
{
protocol: 'https' as const,
hostname: new URL(process.env.NEXT_PUBLIC_BRAND_LOGO_URL!).hostname,
},
]
} catch {
return []
}
})()
: []),
// Brand favicon domain if configured
...(process.env.NEXT_PUBLIC_BRAND_FAVICON_URL
? (() => {
try {
return [
{
protocol: 'https' as const,
hostname: new URL(process.env.NEXT_PUBLIC_BRAND_FAVICON_URL!).hostname,
},
]
} catch {
return []
}
})()
: []),
],
},
typescript: {
ignoreBuildErrors: isTruthy(env.DOCKER_BUILD),
},
output: isTruthy(env.DOCKER_BUILD) ? 'standalone' : undefined,
serverExternalPackages: [
'@1password/sdk',
'unpdf',
'ffmpeg-static',
'fluent-ffmpeg',
'ws',
'isolated-vm',
'@e2b/code-interpreter',
'e2b',
'@earendil-works/pi-coding-agent',
],
outputFileTracingIncludes: {
'/api/tools/stagehand/*': ['./node_modules/ws/**/*'],
'/*': [
'./node_modules/sharp/**/*',
'./node_modules/@img/**/*',
'./lib/execution/sandbox/bundles/*.cjs',
],
},
experimental: {
optimizeCss: !isDev,
turbopackFileSystemCacheForDev: false,
preloadEntriesOnStart: false,
optimizePackageImports: [
'lodash',
'framer-motion',
'reactflow',
'@radix-ui/react-dialog',
'@radix-ui/react-dropdown-menu',
'@radix-ui/react-popover',
'@radix-ui/react-select',
'@radix-ui/react-tabs',
'@radix-ui/react-accordion',
'@radix-ui/react-checkbox',
'@radix-ui/react-switch',
'@radix-ui/react-slider',
'streamdown',
'zod',
],
},
...(isDev && {
allowedDevOrigins: [
...(env.NEXT_PUBLIC_APP_URL
? (() => {
try {
return [new URL(env.NEXT_PUBLIC_APP_URL).host]
} catch {
return []
}
})()
: []),
'localhost:3000',
'localhost:3001',
'127.0.0.1',
'127.0.0.1:3011',
'127.0.0.1:3012',
],
}),
transpilePackages: [
'prettier',
'@react-email/components',
'@react-email/render',
'@t3-oss/env-nextjs',
'@t3-oss/env-core',
'@sim/db',
'@sim/emcn',
'@sim/workflow-renderer',
],
async headers() {
return [
{
// `/public`-served assets keep their path across deploys (no content
// hash), so a shorter TTL + revalidation window bounds how long a
// changed asset can serve stale.
source:
'/((?!api/|_next/static/).*\\.(?:svg|jpg|jpeg|png|gif|ico|webp|avif|woff|woff2|ttf|eot))',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=86400, stale-while-revalidate=604800',
},
],
},
{
source: '/.well-known/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: '*' },
{ key: 'Access-Control-Allow-Methods', value: 'GET, OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Accept' },
],
},
// /api/* CORS is set at runtime in proxy.ts (resolveApiCorsPolicy).
{
source: '/api/workflows/:id/execute',
headers: [
{ key: 'Cross-Origin-Embedder-Policy', value: 'unsafe-none' },
{ key: 'Cross-Origin-Opener-Policy', value: 'unsafe-none' },
{
key: 'Content-Security-Policy',
value: getWorkflowExecutionCSPPolicy(),
},
],
},
{
// Exclude Vercel internal resources and static assets from strict COEP, Google Drive Picker
// and the /demo Cal.com booking embed to prevent 'refused to connect' / slow-load issues
source: '/((?!_next|_vercel|api|favicon.ico|w/.*|workspace/.*|api/tools/drive|demo).*)',
headers: [
{
key: 'Cross-Origin-Embedder-Policy',
value: 'credentialless',
},
{
key: 'Cross-Origin-Opener-Policy',
value: 'same-origin',
},
],
},
{
// For main app routes, Google Drive Picker, the /demo Cal.com embed, and Vercel resources - use permissive policies
source: '/(w/.*|workspace/.*|api/tools/drive|demo.*|_next/.*|_vercel/.*)',
headers: [
{
key: 'Cross-Origin-Embedder-Policy',
value: 'unsafe-none',
},
{
key: 'Cross-Origin-Opener-Policy',
value: 'same-origin-allow-popups',
},
],
},
// Block access to sourcemap files (defense in depth). The trailing
// `$` this rule previously ended with is not a regex anchor in Next's
// `source` matcher (path-to-regexp syntax, not raw regex) - it matched
// a literal `$` character, so this rule never actually fired against
// real `.map` URLs. Next already anchors the compiled pattern at both
// ends, so no trailing anchor is needed here.
//
// Also bounds `.map` files to a short, revalidated TTL rather than
// Next's built-in 1yr immutable default for `_next/static/*` - maps
// are content-hashed like their JS, so this isn't about staleness,
// it's so a future decision to stop shipping `productionBrowserSourceMaps`
// isn't undermined by browsers/edges holding old maps for a year.
{
source: '/(.*)\\.map',
headers: [
{
key: 'x-robots-tag',
value: 'noindex',
},
{
key: 'Cache-Control',
value: 'public, max-age=86400, stale-while-revalidate=604800',
},
],
},
// Chat pages - allow iframe embedding from any origin
{
source: '/chat/:path*',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
// No X-Frame-Options to allow iframe embedding
{
key: 'Content-Security-Policy',
value: getChatEmbedCSPPolicy(),
},
// Permissive CORS for chat requests from embedded chats
{ key: 'Cross-Origin-Embedder-Policy', value: 'unsafe-none' },
{ key: 'Cross-Origin-Opener-Policy', value: 'unsafe-none' },
],
},
// Apply security headers to routes not handled by middleware runtime CSP
// Middleware handles: /, /login, /signup, /workspace/*
// Exclude chat routes which have their own permissive embed headers
{
source: '/((?!workspace|chat|login|signup|$).*)',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'Content-Security-Policy',
value: getMainCSPPolicy(),
},
],
},
]
},
async redirects() {
const redirects = []
// Social link redirects (used in emails to avoid spam filter issues)
redirects.push(
{
source: '/discord',
destination: 'https://discord.gg/Hr4UWYEcTT',
permanent: false,
},
{
source: '/x',
destination: 'https://x.com/simdotai',
permanent: false,
},
{
source: '/github',
destination: 'https://github.com/simstudioai/sim',
permanent: false,
},
{
source: '/team',
destination: 'https://cal.com/emirkarabeg/sim-team',
permanent: false,
}
)
// Redirect /building and /studio to /blog (legacy URL support)
redirects.push(
{
source: '/building/:path*',
destination: 'https://www.sim.ai/blog/:path*',
permanent: true,
},
{
source: '/studio/:path*',
destination: 'https://www.sim.ai/blog/:path*',
permanent: true,
}
)
/**
* The marketing Academy course/lesson pages were removed; content is
* consolidated into the docs site instead. Old course/lesson slugs have
* no equivalent path there, so every sub-path collapses to the new
* landing page rather than forwarding to a path that may not exist.
*/
redirects.push({
source: '/academy/:path*',
destination: 'https://docs.sim.ai/academy',
permanent: true,
})
// Move root feeds to blog namespace
redirects.push(
{
source: '/rss.xml',
destination: '/blog/rss.xml',
permanent: true,
},
{
source: '/sitemap-images.xml',
destination: '/blog/sitemap-images.xml',
permanent: true,
}
)
// Legacy chat URL support: the workspace chat route was renamed from
// `/workspace/:workspaceId/task/:chatId` to `/workspace/:workspaceId/chat/:chatId`.
// Preserve existing bookmarks and deeplinks.
redirects.push({
source: '/workspace/:workspaceId/task/:chatId',
destination: '/workspace/:workspaceId/chat/:chatId',
permanent: true,
})
// Legacy integration slug: the incident.io block's display name was fixed
// from `incidentio` to `incident.io`, which moved its catalog slug.
// Preserve the previously indexed landing URL.
redirects.push({
source: '/integrations/incidentio',
destination: '/integrations/incident-io',
permanent: true,
})
/**
* Legacy integration slug: the SAP block's display name was fixed from
* `SAP S/4HANA` to `SAP S4HANA`, which moved its catalog slug. Preserves
* the previously indexed landing URL.
*/
redirects.push({
source: '/integrations/sap-s-4hana',
destination: '/integrations/sap-s4hana',
permanent: true,
})
/**
* Legacy integration slug: the Cal.com block's display name briefly
* shipped as `CalCom` before being fixed to `Cal Com`/`Cal.com`, which
* moved its catalog slug from `calcom` to `cal-com`.
*/
redirects.push({
source: '/integrations/calcom',
destination: '/integrations/cal-com',
permanent: true,
})
/**
* The partner program page was removed; routes existing links/bookmarks
* to contact instead of leaving a dead, previously-indexed URL.
*/
redirects.push({
source: '/partners',
destination: '/contact',
permanent: true,
})
/**
* AEO/GEO-style posts (listicles, comparisons, how-tos) were split out of
* `/blog` into the dedicated `/library` section so `/blog` stays
* editorial-only. Preserve previously indexed URLs for the moved posts.
*/
for (const slug of [
'best-zapier-alternatives',
'ai-agents-vs-rpa',
'ai-agent-vs-chatbot',
'openai-vs-n8n-vs-sim',
'ai-agent-ideas',
'how-to-create-an-ai-agent',
]) {
redirects.push({
source: `/blog/${slug}`,
destination: `/library/${slug}`,
permanent: true,
})
}
return redirects
},
async rewrites() {
return [
{
source: '/favicon.ico',
destination: '/icon.svg',
},
{
source: '/r/:shortCode',
destination: 'https://go.trybeluga.ai/:shortCode',
},
]
},
}
export default nextConfig