From 6fab3bd789fa22650e85422d2100f6fc1203737a Mon Sep 17 00:00:00 2001 From: Andrew Valleteau Date: Mon, 21 Sep 2026 18:45:15 +0200 Subject: [PATCH] fix(studio): keep sharp out of the self-hosted standalone build (#50658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Bug fix (self-hosted Studio image packaging). ## What is the current behavior? Since the Next 16.3.5 bump, the self-hosted `next build` (`output: 'standalone'`) traces Next's optional `sharp` dependency into `.next/standalone`. Next 16.3.5 upgraded `@vercel/nft` (vercel/next.js#93979), so tracing now follows `module-sync` export conditions, and Next only excludes sharp from the trace when it detects Vercel. Effects: - The self-hosted standalone output carries the `sharp` and `@img/*` native binaries, roughly 10 to 20 percent of the image. - The slim-services Studio image fails: it loads every `.node` file eagerly, and sharp's binary segfaults on linux/amd64 without the matching libvips shared library. `apps/studio` does not depend on `sharp` directly. The last Studio image without sharp in the trace was `2026.09.14`. ## What is the new behavior? Single-file change to `apps/studio/next.config.ts`. When `NEXT_PUBLIC_IS_PLATFORM` is not `'true'`: - `images.unoptimized: true`, so `next/image` renders plain `` and the server 404s `/_next/image` before sharp is ever loaded. This matches what the TanStack build already does via `compat/next/image.tsx`. - `outputFileTracingExcludes` drops `**/node_modules/sharp/**` and `**/node_modules/@img/**` from the standalone trace. Hosted Studio is unchanged: `unoptimized` stays `false`, the exclude key is omitted, and image optimization keeps running on Vercel. Self-hosted CSP is `frame-ancestors 'none'` only, so loading remote avatars directly instead of through `/_next/image` does not hit a CSP rule. Also hoists the existing `isPlatform` const from `redirects()` to module scope. **Why the globs start with `../../`.** Studio builds with Turbopack, which resolves `outputFileTracingExcludes` relative to the app directory. A plain `**/node_modules/sharp/**` becomes `apps/studio/**/node_modules/sharp/**` and never matches the pnpm store hoisted to the monorepo root. Each leading `../` moves the glob root up one level (`relativize_glob` in Next's `crates/next-core/src/util.rs`), so `../../` anchors the pattern at the repo root. The webpack path applies the same globs unprefixed for the server trace, so this is Turbopack-specific. ## Additional context Considered and rejected: a Next.js issue. Next intentionally ships sharp for self-hosted `next start` image optimization and intentionally excludes it only on Vercel. Opting out per app is the supported path. Test plan: - CI: typecheck, lint, Prettier, Studio unit tests, Studio Docker Build. - Verified on this PR with a temporary step in the Studio Docker Build workflow that ran `find` inside the production image for `node_modules/sharp*` and `node_modules/@img*` files. It failed on the first commit (globs rooted at `apps/studio`, image still contained `@img/colour`) and passed once the globs were anchored at the repo root. The step was removed before merge. - Hosted preview should still serve optimized images from `/_next/image`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01SZHcgqB2qNqCvWsFJ9Qvo8 ## Summary by CodeRabbit * **Refactor** * Improved configuration handling for platform and self-hosted deployments. * Self-hosted builds now avoid bundling unnecessary image-processing binaries, while platform deployments retain image optimization support. * Clarified configuration comments. --------- Co-authored-by: Claude Fable 5.1 --- apps/studio/next.config.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/studio/next.config.ts b/apps/studio/next.config.ts index be50142d74f..09c8af478f3 100644 --- a/apps/studio/next.config.ts +++ b/apps/studio/next.config.ts @@ -35,6 +35,8 @@ function getAssetPrefix() { return `${SUPABASE_ASSETS_URL}/${process.env.SITE_NAME}/${process.env.VERCEL_GIT_COMMIT_SHA?.substring(0, 12) ?? 'unknown'}` } +const isPlatform = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' + const marketplaceApiUrl = process.env.NEXT_PUBLIC_MARKETPLACE_API_URL ? new URL(process.env.NEXT_PUBLIC_MARKETPLACE_API_URL) : null @@ -72,7 +74,6 @@ const nextConfig = { // auto-prepends `basePath` to source and destination on its own, // except for the special `/` → basePath bounce below which opts out // via `basePath: false`. - const isPlatform = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' const maintenance = process.env.MAINTENANCE_MODE === 'true' return [ ...(isPlatform ? PLATFORM_REDIRECTS : SELF_HOSTED_REDIRECTS), @@ -145,6 +146,9 @@ const nextConfig = { ] }, images: { + // Self-hosted: serve plain (as the TanStack shim does) so Next never + // loads sharp. Hosted Studio optimizes images on Vercel. + unoptimized: !isPlatform, dangerouslyAllowSVG: false, remotePatterns: [ { @@ -183,6 +187,18 @@ const nextConfig = { : []), ], }, + // Keep Next's optional sharp dependency out of the self-hosted standalone + // output. It is unused with `unoptimized` above, and its native binaries + // break the slim-services Studio image (sharp's .node segfaults without + // libvips). Globs resolve from `apps/studio`; `../../` anchors them at the + // repo root where pnpm hoists the store. + ...(isPlatform + ? {} + : { + outputFileTracingExcludes: { + '*': ['../../**/node_modules/sharp/**/*', '../../**/node_modules/@img/**/*'], + }, + }), transpilePackages: ['ui', 'ui-patterns', 'common', 'shared-data', 'api-types', 'icons'], serverExternalPackages: ['libpg-query'], turbopack: {