Files
Aurora ScharffandGitHub b7f84ed7c7 fix(create-next-app): render filenames in Geist Mono so the preloaded font is used (#95471)
## What?

Render the filename in the "edit the … file" line using **Geist Mono**
across all `create-next-app` templates, so the preloaded font is
actually used.

## Why?

Every fresh app logged a browser warning: the Geist Mono `.woff2` is
preloaded but never used, because the redesign in #81945 dropped the
last monospace element (the old `<code>app/page.tsx</code>`). Rather
than stop preloading the font (this PR's original approach), we _use_ it
— the filename renders in Geist Mono. That clears the warning at its
source and keeps the template showcasing both flagship fonts (two fonts,
no flash of unstyled text).

## How?

- Reverted the `preload: false` change.
- Wrapped the filename in the intro line in a `<code>` rendered in Geist
Mono, styled as a small inline code chip (slightly smaller size, subtle
rounded background, padding) so it reads as a code token rather than
plain heading text:
- Tailwind templates → utility classes (`font-mono` maps to
`--font-geist-mono` via `@theme inline`).
- CSS-module templates → a `.code` class using `var(--font-geist-mono)`;
the chip background uses `color-mix(currentColor …)` so it adapts to
light/dark automatically.
- Applied across all four families (`app`, `app-tw`, `default`,
`default-tw`), JS + TS.

## Screenshots

| | Before | After |
| --------- | --------------------------------------------------- |
-------------------------------------------- |
| **Light** | <img width="2400" height="1520"
alt="cna-geist-mono-before"
src="https://github.com/user-attachments/assets/cf374265-7fdf-4c3b-9f66-05436f966fc9"
/> | <img width="2400" height="1520" alt="cna-geist-mono-after"
src="https://github.com/user-attachments/assets/ae55166d-26d8-4cc2-8268-90c5996d2431"
/> |
| **Dark** | <img width="2400" height="1520"
alt="cna-geist-mono-before-dark"
src="https://github.com/user-attachments/assets/d813adac-85c0-4044-813f-9489b0b4faa7"
/> | <img width="2400" height="1520" alt="cna-geist-mono-after-dark"
src="https://github.com/user-attachments/assets/01199df9-4842-46e0-a73e-a3ba0ef78ad2"
/> |

Before: filename in Geist Sans, preloaded-but-unused warning in the
console. After: filename in Geist Mono, no warning.

<!-- NEXT_JS_LLM_PR -->
2026-07-08 21:43:00 +02:00

207 lines
6.6 KiB
TypeScript

import execa from 'execa'
import { join } from 'path'
import { spawn } from 'child_process'
import { fetchViaHTTP, findPort, killApp } from 'next-test-utils'
import {
resolveTestPkgPaths,
serializeTestPkgPathsEnv,
} from './lib/test-pkg-paths'
export const CNA_PATH = require.resolve('create-next-app/dist/index.js')
/**
* Resolves the path to the packed `next` tarball. Uses NEXT_TEST_PKG_PATHS
* when available (set by run-tests.js), otherwise finds packed.tgz files
* directly from the repo packages/ directory.
*/
export function resolveNextTgzFilename(): string {
const pkgPaths = resolveTestPkgPaths()
const tarballPath = pkgPaths?.get('next')
if (!tarballPath) {
throw new Error(
`Could not find packed "next" tarball. ` +
`Run "pnpm turbo run pack-for-isolated-tests" first, ` +
`or run this test via "node run-tests.js".`
)
}
return tarballPath
}
export const EXAMPLE_REPO = 'https://github.com/vercel/next.js/tree/canary'
export const EXAMPLE_PATH = 'examples/basic-css'
export const FULL_EXAMPLE_PATH = `${EXAMPLE_REPO}/${EXAMPLE_PATH}`
export const DEFAULT_FILES = [
'.gitignore',
'package.json',
'app/page.tsx',
'app/layout.tsx',
'node_modules/next',
]
export const run = async (
args: string[],
nextJSVersion: string,
options:
| execa.Options
| {
reject?: boolean
env?: Record<string, string>
}
) => {
return execa('node', [CNA_PATH].concat(args), {
// tests with options.reject false are expected to exit(1) so don't inherit
stdio: options.reject === false ? 'pipe' : 'inherit',
...options,
env: {
...process.env,
// CNA detects the package manager from `npm_config_user_agent`. CI
// runs jest directly (no `npm_config_user_agent` set) so CNA falls
// back to npm. Locally the test runs under pnpm, which would make
// CNA use pnpm and produce a `.pnpm`-based `node_modules/` that
// doesn't survive being copied into the isolated test directory.
// Clear the variable to mirror CI behavior across environments.
npm_config_user_agent: undefined,
// Forward all packed workspace tarballs so CNA can install siblings
// (`next-rspack`, `eslint-config-next`, ...) from their own tarballs
// when running tests directly (without `run-tests.js`).
NEXT_TEST_PKG_PATHS: serializeTestPkgPathsEnv(),
...options.env,
NEXT_PRIVATE_TEST_VERSION: nextJSVersion,
},
})
}
export const command = (cmd: string, args: string[]) =>
execa(cmd, args, {
stdio: 'inherit',
env: { ...process.env },
})
export async function tryNextDev({
cwd,
projectName,
isApp = true,
isApi = false,
isEmpty = false,
}: {
cwd: string
projectName: string
isApp?: boolean
isApi?: boolean
isEmpty?: boolean
}) {
// The caller wraps this in `useTempDir`, so `cwd` (and the CNA project
// inside it) is already an isolated temp directory that gets removed
// after the test. Running `next build`/`next start` directly inside it
// is safe — `.next/` artifacts are cleaned up with the temp dir.
const dir = join(cwd, projectName)
// CNA installs `eslint-config-next` from the same `packed.tgz` path as
// `next` (both come from `NEXT_PRIVATE_TEST_VERSION`), which causes npm to
// unpack next's tarball into `node_modules/eslint-config-next` and to
// create a `node_modules/.bin/next` shim pointing at that copy. Invoke
// next's bin directly so webpack loaders resolve under `node_modules/next`.
const nextBin = join(dir, 'node_modules/next/dist/bin/next')
const buildResult = await execa('node', [nextBin, 'build'], {
cwd: dir,
stdio: 'inherit',
env: { ...process.env },
reject: false,
})
expect(buildResult.exitCode).toBe(0)
const port = await findPort()
const server = spawn(
'node',
[nextBin, 'start', '-p', String(port), '-H', '127.0.0.1'],
{
cwd: dir,
env: { ...process.env, HOSTNAME: '127.0.0.1' },
stdio: ['ignore', 'pipe', 'pipe'],
}
)
// Freshly installed CNA projects (with tailwind, eslint plugins, etc.)
// can take well over the default 10s to boot `next start`, especially
// under webpack where the built output is larger. Give them generous
// headroom so these tests aren't flaky on loaded CI machines.
const startServerTimeout = 60_000
try {
await new Promise<void>((resolve, reject) => {
const onTimeout = setTimeout(() => {
reject(
new Error(
`next start did not become ready within ${startServerTimeout}ms`
)
)
}, startServerTimeout)
const onReady = () => {
clearTimeout(onTimeout)
resolve()
}
const handleData = (chunk: Buffer) => {
const msg = chunk.toString()
process.stdout.write(msg)
if (/- Local:|Ready in|✓ Ready/i.test(msg)) {
onReady()
}
}
server.stdout!.on('data', handleData)
server.stderr!.on('data', (chunk: Buffer) => {
process.stderr.write(chunk.toString())
})
server.on('exit', (code) => {
clearTimeout(onTimeout)
reject(
new Error(`next start exited before becoming ready (code=${code})`)
)
})
})
const res = await fetchViaHTTP(port, '/')
if (isEmpty || isApi) {
expect(await res.text()).toContain('Hello world!')
} else {
const responseText = await res.text()
// The filename in the intro line is wrapped in a `<code>` element, so
// strip HTML tags and collapse whitespace before matching the copy.
const textContent = responseText
.replace(/<[^>]+>/g, '')
.replace(/\s+/g, ' ')
const hasAppRouterText =
textContent.includes('To get started, edit the page.tsx file.') ||
textContent.includes('To get started, edit the page.js file.')
const hasPagesRouterText =
textContent.includes('To get started, edit the index.tsx file.') ||
textContent.includes('To get started, edit the index.js file.')
expect(hasAppRouterText || hasPagesRouterText).toBe(true)
}
expect(res.status).toBe(200)
if (!isApp && !isEmpty) {
const apiRes = await fetchViaHTTP(port, '/api/hello')
expect(await apiRes.json()).toEqual({ name: 'John Doe' })
expect(apiRes.status).toBe(200)
}
} finally {
await killApp(server).catch(() => {})
}
}
export {
createNextApp,
projectFilesShouldExist,
projectFilesShouldNotExist,
projectShouldHaveNoGitChanges,
shouldBeTemplateProject,
shouldBeJavascriptProject,
shouldBeTypescriptProject,
} from './lib/utils'
export { useTempDir } from '../../lib/use-temp-dir'