Repository: vercel/next.js
Pull request: bundle analyzer: remove geist font in favor of system ui fonts

This replaces Geist as a Google font in favor of using the preferred system ui font, falling back to the system sans serif font.

The various weights of Geist contributed to an unnecessarily large size, impacting the size of the `next` npm package and the analyzer build output.

This also removes the unused `styles/globals.css` file.

Test Plan: Ran `pnpm build` in the analyzer and confirmed that no fonts are included in the output


Changed files:
filename: apps/bundle-analyzer/app/globals.css
patch: @@ -69,8 +69,8 @@
 }
 
 @theme inline {
-  --font-sans: 'Geist', 'Geist Fallback';
-  --font-mono: 'Geist Mono', 'Geist Mono Fallback';
+  --font-sans: system-ui, sans-serif;
+  --font-mono: ui-monospace, monospace;
   --color-background: var(--background);
   --color-foreground: var(--foreground);
   --color-card: var(--card);

filename: apps/bundle-analyzer/app/layout.tsx
patch: @@ -1,12 +1,7 @@
 import type { Metadata } from 'next'
-import { Geist, Geist_Mono } from 'next/font/google'
 import type React from 'react'
 import './globals.css'
 
-// These get used as font-sans and font-mono
-const _geist = Geist({ subsets: ['latin'] })
-const _geistMono = Geist_Mono({ subsets: ['latin'] })
-
 export const metadata: Metadata = {
   title: 'Next.js Bundle Analyzer',
   description:

filename: apps/bundle-analyzer/components/treemap-visualizer.tsx
patch: @@ -13,6 +13,8 @@ import {
 import { SpecialModule } from '@/lib/types'
 import { formatBytes } from '@/lib/utils'
 
+const UI_FONT = 'system-ui, sans-serif'
+
 interface TreemapVisualizerProps {
   analyzeData: AnalyzeData
   sourceIndex: number
@@ -307,7 +309,7 @@ function drawTreemap(
 
         const { titleFontSize } = calculateTitleFontSizes(titleBarHeight)
         ctx.fillStyle = colors.text
-        ctx.font = `600 ${titleFontSize}px sans-serif`
+        ctx.font = `600 ${titleFontSize}px ${UI_FONT}`
         ctx.textAlign = 'left'
         ctx.textBaseline = 'middle'
         ctx.fillText(
@@ -421,22 +423,18 @@ function drawTreemap(
       const sizeFontSize = 10
       const lineHeight = fontSize + 2
 
-      // Check if we have space for both name and size
-      const hasSpaceForSize = rect.height > 50
-
-      ctx.font = `${fontSize}px sans-serif`
+      ctx.font = `${fontSize}px ${UI_FONT}`
       const displayName = truncateTextWithEllipsisIfNeeded(ctx, name, maxWidth)
-
+      const hasSpaceForSize = rect.height > 50
       if (hasSpaceForSize) {
-        ctx.font = `${fontSize}px sans-serif`
         ctx.fillText(
           displayName,
           rect.x + rect.width / 2,
           rect.y + rect.height / 2 - lineHeight / 2
         )
 
         ctx.globalAlpha = opacity * 0.75
-        ctx.font = `${sizeFontSize}px sans-serif`
+        ctx.font = `${sizeFontSize}px ${UI_FONT}`
         ctx.fillText(
           sizeText,
           rect.x + rect.width / 2,
@@ -445,7 +443,6 @@ function drawTreemap(
         ctx.globalAlpha = opacity
       } else {
         // Only name fits, draw it centered
-        ctx.font = `${fontSize}px sans-serif`
         ctx.fillText(
           displayName,
           rect.x + rect.width / 2,
@@ -497,12 +494,12 @@ function drawTreemap(
       ctx.textBaseline = 'middle'
 
       // Measure size text first to reserve space
-      ctx.font = `${sizeFontSize}px sans-serif`
+      ctx.font = `${sizeFontSize}px ${UI_FONT}`
       const sizeWidth = measureTextCached(ctx, sizeText)
 
       const nameX = rect.x + 8
       const availableNameWidth = Math.max(0, rect.width - 16 - sizeWidth - gap)
-      ctx.font = `600 ${titleFontSize}px sans-serif`
+      ctx.font = `600 ${titleFontSize}px ${UI_FONT}`
       const displayName = truncateTextWithEllipsisIfNeeded(
         ctx,
         name,
@@ -518,7 +515,7 @@ function drawTreemap(
 
       // Only draw size text if it fits within bounds
       if (sizeX + sizeWidth <= rect.x + rect.width - 8) {
-        ctx.font = `${sizeFontSize}px sans-serif`
+        ctx.font = `${sizeFontSize}px ${UI_FONT}`
         ctx.fillStyle = colors.textMuted
         ctx.fillText(sizeText, sizeX, centerY)
       }
@@ -566,12 +563,12 @@ function drawTreemap(
 
       ctx.textBaseline = 'middle'
 
-      ctx.font = `${sizeFontSize}px sans-serif`
+      ctx.font = `${sizeFontSize}px ${UI_FONT}`
       const sizeWidth = measureTextCached(ctx, sizeText)
 
       const nameX = rect.x + 8
       const availableNameWidth = Math.max(0, rect.width - 16 - sizeWidth - gap)
-      ctx.font = `600 ${titleFontSize}px sans-serif`
+      ctx.font = `600 ${titleFontSize}px ${UI_FONT}`
       const displayName = truncateTextWithEllipsisIfNeeded(
         ctx,
         name,
@@ -587,7 +584,7 @@ function drawTreemap(
 
       // Only draw size text if it fits within bounds
       if (sizeX + sizeWidth <= rect.x + rect.width - 8) {
-        ctx.font = `${sizeFontSize}px sans-serif`
+        ctx.font = `${sizeFontSize}px ${UI_FONT}`
         ctx.fillStyle = colors.textMuted
         ctx.fillText(sizeText, sizeX, centerY)
       }

filename: apps/bundle-analyzer/styles/globals.css
patch: @@ -1,86 +0,0 @@
-@import 'tailwindcss';
-@import 'tw-animate-css';
-
-@custom-variant dark (&:is(.dark *));
-
-:root {
-  --background: oklch(1 0 0);
-  --foreground: oklch(0.145 0 0);
-  --popover: oklch(1 0 0);
-  --popover-foreground: oklch(0.145 0 0);
-  --primary: oklch(0.205 0 0);
-  --primary-foreground: oklch(0.985 0 0);
-  --secondary: oklch(0.97 0 0);
-  --secondary-foreground: oklch(0.205 0 0);
-  --muted: oklch(0.97 0 0);
-  --muted-foreground: oklch(0.556 0 0);
-  --accent: oklch(0.97 0 0);
-  --accent-foreground: oklch(0.205 0 0);
-  --destructive: oklch(0.577 0.245 27.325);
-  --destructive-foreground: oklch(0.577 0.245 27.325);
-  --border: oklch(0.922 0 0);
-  --input: oklch(0.922 0 0);
-  --ring: oklch(0.708 0 0);
-  --polyfill: oklch(0.478 0.0185 252.37);
-  --polyfill-foreground: oklch(0.99 0 0);
-  --radius: 0.625rem;
-}
-
-.dark {
-  --background: oklch(0.145 0 0);
-  --foreground: oklch(0.985 0 0);
-  --popover: oklch(0.145 0 0);
-  --popover-foreground: oklch(0.985 0 0);
-  --primary: oklch(0.985 0 0);
-  --primary-foreground: oklch(0.205 0 0);
-  --secondary: oklch(0.269 0 0);
-  --secondary-foreground: oklch(0.985 0 0);
-  --muted: oklch(0.269 0 0);
-  --muted-foreground: oklch(0.708 0 0);
-  --accent: oklch(0.269 0 0);
-  --accent-foreground: oklch(0.985 0 0);
-  --destructive: oklch(0.396 0.141 25.723);
-  --destructive-foreground: oklch(0.637 0.237 25.331);
-  --border: oklch(0.269 0 0);
-  --input: oklch(0.269 0 0);
-  --ring: oklch(0.439 0 0);
-  --polyfill: oklch(0.478 0.0185 252.37);
-  --polyfill-foreground: oklch(0.99 0 0);
-}
-
-@theme inline {
-  --font-sans: 'Geist', 'Geist Fallback';
-  --font-mono: 'Geist Mono', 'Geist Mono Fallback';
-  --color-background: var(--background);
-  --color-foreground: var(--foreground);
-  --color-popover: var(--popover);
-  --color-popover-foreground: var(--popover-foreground);
-  --color-primary: var(--primary);
-  --color-primary-foreground: var(--primary-foreground);
-  --color-secondary: var(--secondary);
-  --color-secondary-foreground: var(--secondary-foreground);
-  --color-muted: var(--muted);
-  --color-muted-foreground: var(--muted-foreground);
-  --color-accent: var(--accent);
-  --color-accent-foreground: var(--accent-foreground);
-  --color-destructive: var(--destructive);
-  --color-destructive-foreground: var(--destructive-foreground);
-  --color-border: var(--border);
-  --color-input: var(--input);
-  --color-ring: var(--ring);
-  --color-polyfill: var(--polyfill);
-  --color-polyfill-foreground: var(--polyfill-foreground);
-  --radius-sm: calc(var(--radius) - 4px);
-  --radius-md: calc(var(--radius) - 2px);
-  --radius-lg: var(--radius);
-  --radius-xl: calc(var(--radius) + 4px);
-}
-
-@layer base {
-  * {
-    @apply border-border outline-ring/50;
-  }
-  body {
-    @apply bg-background text-foreground;
-  }
-}