diff --git a/apps/design-system/app/layout.tsx b/apps/design-system/app/layout.tsx
index 806c5ae0d95..5f0a568bd68 100644
--- a/apps/design-system/app/layout.tsx
+++ b/apps/design-system/app/layout.tsx
@@ -11,12 +11,14 @@ import { inter, manrope, sourceCodePro } from '@/lib/fonts'
const className = `${inter.variable} ${manrope.variable} ${sourceCodePro.variable}`
+const FAVICON_ROUTE = process.env.NODE_ENV === 'development' ? '/favicon/local' : '/favicon'
+
export const metadata: Metadata = {
applicationName: 'Supabase Design System',
title: 'Supabase Design System',
description: 'Design resources for building consistent user experiences at Supabase.',
- icons: genFaviconData(BASE_PATH),
- manifest: `${BASE_PATH}/favicon/manifest.json`,
+ icons: genFaviconData(BASE_PATH, FAVICON_ROUTE),
+ manifest: `${BASE_PATH}${FAVICON_ROUTE}/manifest.json`,
}
export const viewport: Viewport = {
diff --git a/apps/design-system/public/favicon/local/android-icon-192x192.png b/apps/design-system/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/design-system/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/design-system/public/favicon/local/android-icon-512x512.png b/apps/design-system/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/design-system/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/design-system/public/favicon/local/apple-icon-180x180.png b/apps/design-system/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/design-system/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/design-system/public/favicon/local/favicon.ico b/apps/design-system/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/design-system/public/favicon/local/favicon.ico differ
diff --git a/apps/design-system/public/favicon/local/manifest.json b/apps/design-system/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..1d6763c029d
--- /dev/null
+++ b/apps/design-system/public/favicon/local/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Supabase Design System",
+ "short_name": "Supabase Design System",
+ "description": "Design resources for building consistent user experiences at Supabase.",
+ "display": "standalone",
+ "theme_color": "#1C1C1C",
+ "background_color": "#1C1C1C",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/apps/design-system/turbo.jsonc b/apps/design-system/turbo.jsonc
index b61bfcc7955..29bec7c187d 100644
--- a/apps/design-system/turbo.jsonc
+++ b/apps/design-system/turbo.jsonc
@@ -4,7 +4,7 @@
"tasks": {
"build": {
"dependsOn": ["^build"],
- "env": ["NEXT_PUBLIC_BASE_PATH", "NEXT_PUBLIC_APP_URL"],
+ "env": ["NODE_ENV", "NEXT_PUBLIC_BASE_PATH", "NEXT_PUBLIC_APP_URL"],
"outputs": [".next/**", "!.next/cache/**", "!.next/dev/**/*", ".velite/**"],
},
},
diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx
index c386e621df1..f96557fa3ff 100644
--- a/apps/docs/app/layout.tsx
+++ b/apps/docs/app/layout.tsx
@@ -19,14 +19,16 @@ const { metadataApplicationName, metadataTitle } = getCustomContent([
'metadata:title',
])
+const FAVICON_ROUTE = process.env.NODE_ENV === 'development' ? '/favicon/local' : '/favicon'
+
const metadata: Metadata = {
applicationName: metadataApplicationName,
title: metadataTitle,
description:
'Supabase is the Postgres development platform providing all the backend features you need to build a product.',
metadataBase: new URL('https://supabase.com'),
- icons: genFaviconData(BASE_PATH),
- manifest: `${BASE_PATH}/favicon/manifest.json`,
+ icons: genFaviconData(BASE_PATH, FAVICON_ROUTE),
+ manifest: `${BASE_PATH}${FAVICON_ROUTE}/manifest.json`,
robots: {
index: IS_PRODUCTION,
follow: IS_PRODUCTION,
diff --git a/apps/docs/public/favicon/local/android-icon-192x192.png b/apps/docs/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/docs/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/docs/public/favicon/local/android-icon-512x512.png b/apps/docs/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/docs/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/docs/public/favicon/local/apple-icon-180x180.png b/apps/docs/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/docs/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/docs/public/favicon/local/favicon.ico b/apps/docs/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/docs/public/favicon/local/favicon.ico differ
diff --git a/apps/docs/public/favicon/local/manifest.json b/apps/docs/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..8b55cf1ecb1
--- /dev/null
+++ b/apps/docs/public/favicon/local/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Supabase",
+ "short_name": "Supabase",
+ "description": "The Postgres Development Platform.",
+ "display": "standalone",
+ "theme_color": "#1C1C1C",
+ "background_color": "#1C1C1C",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/apps/kb/public/favicon/local/android-icon-192x192.png b/apps/kb/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/kb/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/kb/public/favicon/local/android-icon-512x512.png b/apps/kb/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/kb/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/kb/public/favicon/local/apple-icon-180x180.png b/apps/kb/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/kb/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/kb/public/favicon/local/favicon.ico b/apps/kb/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/kb/public/favicon/local/favicon.ico differ
diff --git a/apps/kb/public/favicon/local/manifest.json b/apps/kb/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..815234ebf8f
--- /dev/null
+++ b/apps/kb/public/favicon/local/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "Supabase Knowledge Base",
+ "short_name": "Supabase",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/apps/kb/src/layouts/Layout.astro b/apps/kb/src/layouts/Layout.astro
index b791c568e2e..959404eef0b 100644
--- a/apps/kb/src/layouts/Layout.astro
+++ b/apps/kb/src/layouts/Layout.astro
@@ -10,7 +10,7 @@ interface Props {
}
const { title, description } = Astro.props
-const faviconRoute = '/favicon'
+const faviconRoute = import.meta.env.DEV ? '/favicon/local' : '/favicon'
---
diff --git a/apps/learn/app/layout.tsx b/apps/learn/app/layout.tsx
index 67604f9773e..c76e2ec5d75 100644
--- a/apps/learn/app/layout.tsx
+++ b/apps/learn/app/layout.tsx
@@ -14,13 +14,15 @@ const inter = Inter({ subsets: ['latin'] })
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? ''
+const FAVICON_ROUTE = process.env.NODE_ENV === 'development' ? '/favicon/local' : '/favicon'
+
export const metadata: Metadata = {
applicationName: 'Learn Supabase',
title: 'Learn Supabase',
description: 'Learn Supabase.',
metadataBase: new URL('https://supabase.com/learn'),
- icons: genFaviconData(BASE_PATH),
- manifest: `${BASE_PATH}/favicon/manifest.json`,
+ icons: genFaviconData(BASE_PATH, FAVICON_ROUTE),
+ manifest: `${BASE_PATH}${FAVICON_ROUTE}/manifest.json`,
openGraph: {
type: 'article',
authors: 'Supabase',
diff --git a/apps/learn/public/favicon/local/android-icon-192x192.png b/apps/learn/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/learn/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/learn/public/favicon/local/android-icon-512x512.png b/apps/learn/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/learn/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/learn/public/favicon/local/apple-icon-180x180.png b/apps/learn/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/learn/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/learn/public/favicon/local/favicon.ico b/apps/learn/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/learn/public/favicon/local/favicon.ico differ
diff --git a/apps/learn/public/favicon/local/manifest.json b/apps/learn/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..8b55cf1ecb1
--- /dev/null
+++ b/apps/learn/public/favicon/local/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Supabase",
+ "short_name": "Supabase",
+ "description": "The Postgres Development Platform.",
+ "display": "standalone",
+ "theme_color": "#1C1C1C",
+ "background_color": "#1C1C1C",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/apps/learn/turbo.jsonc b/apps/learn/turbo.jsonc
index d6b9b97dfe5..827f89325c7 100644
--- a/apps/learn/turbo.jsonc
+++ b/apps/learn/turbo.jsonc
@@ -4,6 +4,7 @@
"tasks": {
"build": {
"dependsOn": ["^build"],
+ "env": ["NODE_ENV"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**/*", "!.next/dev/**/*", ".velite/**"],
},
},
diff --git a/apps/lite-studio/app/root.tsx b/apps/lite-studio/app/root.tsx
index b0fab81420b..c2c3ab7e70c 100644
--- a/apps/lite-studio/app/root.tsx
+++ b/apps/lite-studio/app/root.tsx
@@ -5,6 +5,22 @@ import type { Route } from './+types/root'
import '../styles/globals.css'
export const links: Route.LinksFunction = () => [
+ ...(import.meta.env.DEV
+ ? [
+ {
+ rel: 'icon',
+ type: 'image/x-icon',
+ sizes: '16x16 32x32 48x48',
+ href: '/favicon/local/favicon.ico',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '180x180',
+ href: '/favicon/local/apple-icon-180x180.png',
+ },
+ { rel: 'manifest', href: '/favicon/local/manifest.json' },
+ ]
+ : [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]),
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
rel: 'preconnect',
diff --git a/apps/lite-studio/public/favicon/local/android-icon-192x192.png b/apps/lite-studio/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/lite-studio/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/lite-studio/public/favicon/local/android-icon-512x512.png b/apps/lite-studio/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/lite-studio/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/lite-studio/public/favicon/local/apple-icon-180x180.png b/apps/lite-studio/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/lite-studio/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/lite-studio/public/favicon/local/favicon.ico b/apps/lite-studio/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/lite-studio/public/favicon/local/favicon.ico differ
diff --git a/apps/lite-studio/public/favicon/local/manifest.json b/apps/lite-studio/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..cb57b9e8531
--- /dev/null
+++ b/apps/lite-studio/public/favicon/local/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "Supabase Lite Studio",
+ "short_name": "Supabase",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/apps/studio/lib/cli-favicon.test.ts b/apps/studio/lib/cli-favicon.test.ts
new file mode 100644
index 00000000000..86b0b90dfdf
--- /dev/null
+++ b/apps/studio/lib/cli-favicon.test.ts
@@ -0,0 +1,22 @@
+import { afterEach, describe, expect, it, vi } from 'vitest'
+
+import { getCLIFaviconRoute } from './cli-favicon'
+
+vi.mock('@tanstack/react-start', () => ({
+ createServerFn: () => ({ handler: (handler: () => string) => handler }),
+}))
+
+afterEach(() => vi.unstubAllEnvs())
+
+describe('CLI favicon route', () => {
+ it('reads the CLI version at request time, after the module has loaded', async () => {
+ vi.stubEnv('CURRENT_CLI_VERSION', '')
+ expect(await getCLIFaviconRoute()).toBe('/favicon')
+
+ vi.stubEnv('CURRENT_CLI_VERSION', '2.50.0')
+ expect(await getCLIFaviconRoute()).toBe('/favicon/local')
+
+ vi.stubEnv('CURRENT_CLI_VERSION', '')
+ expect(await getCLIFaviconRoute()).toBe('/favicon')
+ })
+})
diff --git a/apps/studio/lib/cli-favicon.ts b/apps/studio/lib/cli-favicon.ts
new file mode 100644
index 00000000000..f5c162cf908
--- /dev/null
+++ b/apps/studio/lib/cli-favicon.ts
@@ -0,0 +1,6 @@
+import { createServerFn } from '@tanstack/react-start'
+
+// Read at request time because the CLI sets this after the image is built.
+export const getCLIFaviconRoute = createServerFn({ method: 'GET' }).handler(() =>
+ process.env.CURRENT_CLI_VERSION ? '/favicon/local' : '/favicon'
+)
diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx
index 9dda7bd2968..8a88cd05d20 100644
--- a/apps/studio/pages/_app.tsx
+++ b/apps/studio/pages/_app.tsx
@@ -177,6 +177,13 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
// [Joshen] Should target hosted staging, local dev, and local CLI only
const isNonProdEnv = (IS_PLATFORM && process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod') || isCLI
+ const isLocalEnv =
+ process.env.NODE_ENV === 'development' ||
+ process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' ||
+ isCLI
+ let faviconRoute = '/favicon'
+ if (isNonProdEnv) faviconRoute = '/favicon/staging'
+ if (isLocalEnv) faviconRoute = '/favicon/local'
const checkCliEnvironment = async () => {
const data = await getCLIReleaseVersion()
@@ -220,7 +227,7 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
diff --git a/apps/studio/public/favicon/local/android-icon-192x192.png b/apps/studio/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/studio/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/studio/public/favicon/local/android-icon-512x512.png b/apps/studio/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/studio/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/studio/public/favicon/local/apple-icon-180x180.png b/apps/studio/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/studio/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/studio/public/favicon/local/favicon.ico b/apps/studio/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/studio/public/favicon/local/favicon.ico differ
diff --git a/apps/studio/public/favicon/local/manifest.json b/apps/studio/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..ea9c17ccab3
--- /dev/null
+++ b/apps/studio/public/favicon/local/manifest.json
@@ -0,0 +1,27 @@
+{
+ "name": "Supabase Studio",
+ "short_name": "Supabase",
+ "description": "The Postgres Development Platform.",
+ "display_override": [
+ "minimal-ui"
+ ],
+ "display": "standalone",
+ "theme_color": "#1C1C1C",
+ "background_color": "#1C1C1C",
+ "scope": "/",
+ "start_url": "/",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/apps/studio/routes/__root.tsx b/apps/studio/routes/__root.tsx
index a6d71fcae2a..8a63fd3b6d9 100644
--- a/apps/studio/routes/__root.tsx
+++ b/apps/studio/routes/__root.tsx
@@ -79,6 +79,7 @@ import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedOrganizationCreatedAtQuery } from '@/hooks/misc/useSelectedOrganizationCreatedAt'
import { AuthProvider } from '@/lib/auth'
+import { getCLIFaviconRoute } from '@/lib/cli-favicon'
import { toUnixSecondsString } from '@/lib/configcat-attributes'
import { configureMonacoLoader } from '@/lib/configure-monaco-loader'
import { API_URL, BASE_PATH, IS_PLATFORM, useDefaultProvider } from '@/lib/constants'
@@ -204,11 +205,17 @@ const devToolbarExtraTabs: ExtraTab[] = IS_DEV_TOOLBAR_ENABLED
configureMonacoLoader()
-const FAVICON_ROUTE = IS_NON_PROD_ENV ? '/favicon/staging' : '/favicon'
+// Build-time branding; CLI mode is resolved at runtime by the root loader.
+let FAVICON_ROUTE = '/favicon'
+if (IS_PLATFORM && process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod')
+ FAVICON_ROUTE = '/favicon/staging'
+if (process.env.NODE_ENV === 'development' || process.env.NEXT_PUBLIC_ENVIRONMENT === 'local') {
+ FAVICON_ROUTE = '/favicon/local'
+}
const THEME_COLOR = '1E1E1E'
const APPLICATION_NAME = 'Supabase Studio'
-function buildRootHead() {
+function buildRootHead(faviconRoute = FAVICON_ROUTE) {
const meta: Array> = [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'initial-scale=1.0, width=device-width' },
@@ -220,8 +227,8 @@ function buildRootHead() {
]
const links: Array> = [
- ...genFaviconLinks(BASE_PATH, FAVICON_ROUTE),
- { rel: 'manifest', href: `${BASE_PATH}${FAVICON_ROUTE}/manifest.json` },
+ ...genFaviconLinks(BASE_PATH, faviconRoute),
+ { rel: 'manifest', href: `${BASE_PATH}${faviconRoute}/manifest.json` },
]
if (IS_PLATFORM) {
@@ -279,7 +286,6 @@ function ErrorBoundaryRoute({ error }: ErrorComponentProps) {
}
export const Route = createRootRouteWithContext()({
- head: buildRootHead,
// Mirrors the redirect rules in `next.config.ts` / `vercel.ts`. Vercel's
// edge layer already handles these for the platform deploy; this is the
// self-hosted (Node-server) fallback and the client-side safety net.
@@ -308,6 +314,15 @@ export const Route = createRootRouteWithContext()({
statusCode: match.permanent ? 308 : 307,
})
},
+ loader: async (): Promise => {
+ if (IS_PLATFORM || FAVICON_ROUTE !== '/favicon') return FAVICON_ROUTE
+ try {
+ return await getCLIFaviconRoute()
+ } catch {
+ return FAVICON_ROUTE
+ }
+ },
+ head: ({ loaderData }) => buildRootHead(loaderData),
component: RootComponent,
shellComponent: RootDocument,
notFoundComponent: NotFound,
diff --git a/apps/ui-library/app/layout.tsx b/apps/ui-library/app/layout.tsx
index c6105811fa2..13df9e4083d 100644
--- a/apps/ui-library/app/layout.tsx
+++ b/apps/ui-library/app/layout.tsx
@@ -20,13 +20,15 @@ const sourceCodePro = Source_Code_Pro({
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? ''
+const FAVICON_ROUTE = process.env.NODE_ENV === 'development' ? '/favicon/local' : '/favicon'
+
export const metadata: Metadata = {
applicationName: 'Supabase Library',
title: 'Supabase Library',
description: 'Supabase blocks and starter apps for authentication, storage, realtime, and more',
metadataBase: new URL('https://supabase.com'),
- icons: genFaviconData(BASE_PATH),
- manifest: `${BASE_PATH}/favicon/manifest.json`,
+ icons: genFaviconData(BASE_PATH, FAVICON_ROUTE),
+ manifest: `${BASE_PATH}${FAVICON_ROUTE}/manifest.json`,
openGraph: {
type: 'article',
authors: 'Supabase',
diff --git a/apps/ui-library/public/favicon/local/android-icon-192x192.png b/apps/ui-library/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/ui-library/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/ui-library/public/favicon/local/android-icon-512x512.png b/apps/ui-library/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/ui-library/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/ui-library/public/favicon/local/apple-icon-180x180.png b/apps/ui-library/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/ui-library/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/ui-library/public/favicon/local/favicon.ico b/apps/ui-library/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/ui-library/public/favicon/local/favicon.ico differ
diff --git a/apps/ui-library/public/favicon/local/manifest.json b/apps/ui-library/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..8b55cf1ecb1
--- /dev/null
+++ b/apps/ui-library/public/favicon/local/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Supabase",
+ "short_name": "Supabase",
+ "description": "The Postgres Development Platform.",
+ "display": "standalone",
+ "theme_color": "#1C1C1C",
+ "background_color": "#1C1C1C",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx
index 1b834bd6278..7bc296cdc12 100644
--- a/apps/www/app/layout.tsx
+++ b/apps/www/app/layout.tsx
@@ -12,6 +12,8 @@ import { APP_NAME, DEFAULT_META_DESCRIPTION } from '@/lib/constants'
const site_title = `${APP_NAME} | The Postgres Development Platform`
+const FAVICON_ROUTE = process.env.NODE_ENV === 'development' ? '/favicon/local' : '/favicon'
+
export const metadata: Metadata = {
title: site_title,
description: DEFAULT_META_DESCRIPTION,
@@ -34,8 +36,8 @@ export const metadata: Metadata = {
card: 'summary_large_image',
},
// www serves from the site root (`basePath: ''` in next.config.mjs)
- icons: genFaviconData(''),
- manifest: '/favicon/manifest.json',
+ icons: genFaviconData('', FAVICON_ROUTE),
+ manifest: `${FAVICON_ROUTE}/manifest.json`,
}
export const viewport: Viewport = {
diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx
index ea81476c200..b8317ee95f0 100644
--- a/apps/www/pages/_app.tsx
+++ b/apps/www/pages/_app.tsx
@@ -46,7 +46,7 @@ export default function App({ Component, pageProps }: AppProps) {
const forceDarkMode = isDarkLaunchWeek
const applicationName = 'Supabase'
- const faviconRoute = '/favicon'
+ const faviconRoute = process.env.NODE_ENV === 'development' ? '/favicon/local' : '/favicon'
const themeColor = DEFAULT_FAVICON_THEME_COLOR
// Advertise the .md version for AI agents on pages that have one.
diff --git a/apps/www/public/favicon/local/android-icon-192x192.png b/apps/www/public/favicon/local/android-icon-192x192.png
new file mode 100644
index 00000000000..a174cf0468a
Binary files /dev/null and b/apps/www/public/favicon/local/android-icon-192x192.png differ
diff --git a/apps/www/public/favicon/local/android-icon-512x512.png b/apps/www/public/favicon/local/android-icon-512x512.png
new file mode 100644
index 00000000000..a5fc7d6b949
Binary files /dev/null and b/apps/www/public/favicon/local/android-icon-512x512.png differ
diff --git a/apps/www/public/favicon/local/apple-icon-180x180.png b/apps/www/public/favicon/local/apple-icon-180x180.png
new file mode 100644
index 00000000000..775ce59ec9b
Binary files /dev/null and b/apps/www/public/favicon/local/apple-icon-180x180.png differ
diff --git a/apps/www/public/favicon/local/favicon.ico b/apps/www/public/favicon/local/favicon.ico
new file mode 100644
index 00000000000..7cc4cd192a9
Binary files /dev/null and b/apps/www/public/favicon/local/favicon.ico differ
diff --git a/apps/www/public/favicon/local/manifest.json b/apps/www/public/favicon/local/manifest.json
new file mode 100644
index 00000000000..20493c96227
--- /dev/null
+++ b/apps/www/public/favicon/local/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Supabase",
+ "short_name": "Supabase",
+ "description": "The Postgres development platform.",
+ "display": "standalone",
+ "theme_color": "#1C1C1C",
+ "background_color": "#1C1C1C",
+ "icons": [
+ {
+ "src": "android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "android-icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
diff --git a/assets/favicons/README.md b/assets/favicons/README.md
index 71a1ace0a3f..21512cd9d19 100644
--- a/assets/favicons/README.md
+++ b/assets/favicons/README.md
@@ -1,18 +1,19 @@
# Favicons
-The vector sources are exported from [Favicons](https://www.figma.com/design/WCja3lpEj1DeunV1d5zu5D/Design-System?node-id=4691-1489). Each master is a native 512×512 Figma component with a 96px background radius and 60% corner smoothing, equivalent to 3px at 16px. The 16, 32 and 48px previews are scaled instances of these components. Scale the vectors, never a small raster export. Preserve the variant-specific bolt gradients, shadows and proportions.
+The vector sources are exported from [Favicons](https://www.figma.com/design/WCja3lpEj1DeunV1d5zu5D/Design-System?node-id=4691-1489). Each master is a native 512×512 Figma component with a 96px background radius and 60% corner smoothing, equivalent to 3px at 16px. The 16, 32 and 48px previews are scaled instances of these components. Scale the vectors, never a small raster export. Preserve the variant-specific bolt gradients, shadows, proportions and local inset edge detail.
| Source | Original node | Master node | Consumers |
| ---------------- | ------------- | ----------- | ----------------------------------------------------------------- |
| `production.svg` | `4691:1519` | `4696:14` | Studio production and self-hosted, WWW, UI Library, Design System |
| `docs.svg` | `4691:1517` | `4696:15` | Docs, Learn, Knowledge Base |
| `staging.svg` | `4691:1512` | `4696:17` | Hosted Studio non-production |
+| `local.svg` | `4691:1509` | `4696:19` | Local development in all apps, plus Studio CLI |
-[Favicons (old)](https://www.figma.com/design/WCja3lpEj1DeunV1d5zu5D/Design-System?node-id=4431-5215) contains historical explorations, not interchangeable production assets.
+The local artwork also corresponds to the manually revised [512px reference](https://www.figma.com/design/WCja3lpEj1DeunV1d5zu5D/Design-System?node-id=4691-1483). [Favicons (old)](https://www.figma.com/design/WCja3lpEj1DeunV1d5zu5D/Design-System?node-id=4431-5215) contains historical explorations, not interchangeable production assets.
## Generation
-The SVG backgrounds are flattened Figma vectors so 60% corner smoothing survives export.
+The SVG backgrounds are flattened Figma vectors so 60% corner smoothing survives export. Local corner grid marks are filled vectors, avoiding differences in dashed-stroke rendering.
From the repository root, after `pnpm install`:
@@ -49,11 +50,11 @@ copy.remove()
- `android-icon-192x192.png` and `android-icon-512x512.png`: rounded artwork with transparent corners, manifest purpose `any`, not `maskable`.
- `manifest.json`: relative icon URLs resolve beside the manifest under any application base path. Existing non-icon settings are retained.
-Learn, UI Library and Design System use explicit layout declarations. Their `app/favicon.ico` copies are removed to avoid duplicate declarations. Shared declarations in `packages/common/MetaFavicons/icons.ts` serve Next.js pages/app routers, Studio TanStack and KB Astro. Applications select routes and detect their environments. Existing Studio environment routing is retained.
+Learn, UI Library and Design System use explicit layout declarations. Their `app/favicon.ico` copies are removed so Next.js does not also advertise production artwork during local development. Shared declarations in `packages/common/MetaFavicons/icons.ts` serve Next.js pages/app routers, Studio TanStack and KB Astro. Applications select routes and detect their environments. Next Studio retains its runtime CLI discovery; TanStack reads CLI mode on the server at request time and passes the favicon route through its root loader.
## Other artwork
-WWW's historical Launch Week X favicon directory contains separate campaign artwork and remains unchanged. Its isolated legacy component has no current imports. Lite Studio keeps its independent production root ICO.
+WWW's historical Launch Week X favicon directory contains separate campaign artwork and remains unchanged. Its isolated legacy component has no current imports. Lite Studio keeps its independent production root ICO and explicitly selects the blue set during local development.
## Validation
@@ -62,3 +63,7 @@ node scripts/validate-favicons.mjs
```
The script checks dimensions, opacity, ICO entries and manifest icon URLs under different base paths. Check for duplicate icon declarations in the rendered page head separately. Inspect 16px and 32px renders against light and dark browser chrome as well as larger masters.
+
+## Local development
+
+Next.js apps select `/favicon/local` when `NODE_ENV` is `development`; Astro and React Router use `import.meta.env.DEV`. Studio also recognises its explicit local environment and CLI mode. Production builds retain each app's normal branding. Manifest URLs follow the selected route and application base path.
diff --git a/assets/favicons/local.svg b/assets/favicons/local.svg
new file mode 100644
index 00000000000..75424d198a1
--- /dev/null
+++ b/assets/favicons/local.svg
@@ -0,0 +1,17 @@
+
diff --git a/scripts/favicon-targets.mjs b/scripts/favicon-targets.mjs
index ee59f00c3c3..b55e9a64111 100644
--- a/scripts/favicon-targets.mjs
+++ b/scripts/favicon-targets.mjs
@@ -1,6 +1,9 @@
export const faviconTargets = [
['production', 'apps/studio/public/favicon'],
['staging', 'apps/studio/public/favicon/staging'],
+ ...['studio', 'www', 'docs', 'learn', 'ui-library', 'design-system', 'kb', 'lite-studio'].map(
+ (app) => ['local', `apps/${app}/public/favicon/local`]
+ ),
...['www', 'ui-library', 'design-system'].map((app) => [
'production',
`apps/${app}/public/favicon`,
diff --git a/scripts/generate-favicons.mjs b/scripts/generate-favicons.mjs
index 889c2224686..d9328ef85eb 100644
--- a/scripts/generate-favicons.mjs
+++ b/scripts/generate-favicons.mjs
@@ -32,7 +32,7 @@ for (const [variant, relativeDirectory] of faviconTargets) {
const directory = path.join(root, relativeDirectory)
await mkdir(directory, { recursive: true })
const svg = await readFile(path.join(root, `assets/favicons/${variant}.svg`))
- const background = variant === 'staging' ? '#FFFFFF' : '#1C1C1C'
+ const background = variant === 'staging' ? '#FFFFFF' : variant === 'local' ? '#087BFF' : '#1C1C1C'
const render = (size) => sharp(svg).resize(size, size)
const images = await Promise.all(
[16, 32, 48].map(async (size) => ({ size, data: await render(size).png().toBuffer() }))
@@ -60,4 +60,4 @@ for (const [variant, relativeDirectory] of faviconTargets) {
}
// Next.js layouts declare their icons explicitly. Do not add app/favicon.ico,
-// which would duplicate the shared icon declarations.
+// which would also advertise the production artwork during local development.
diff --git a/scripts/validate-favicons.mjs b/scripts/validate-favicons.mjs
index c4fada4f935..7e6a6786c05 100644
--- a/scripts/validate-favicons.mjs
+++ b/scripts/validate-favicons.mjs
@@ -14,6 +14,10 @@ for (const [variant, directory] of faviconTargets) {
const svg = await readFile(path.join(root, `assets/favicons/${variant}.svg`), 'utf8')
assert.match(svg, /width="512" height="512"/)
assert.match(svg, /M0 153\.6C0 99\.835/)
+ if (variant === 'local') {
+ assert.doesNotMatch(svg, /stroke-dasharray|stroke-width/)
+ assert.match(svg, /