From a780f2595db86a773be0be1fefcbd9cbab2e8fc2 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Wed, 9 Nov 2022 11:53:42 -0400 Subject: [PATCH] Migrate error messages to new format (and misc error improvements) (#5316) * Migrate messages to errors data file * Move errors to new 'error database' for easier improvements * Fix tests * Add changeset * Remove unnecessary console.log * Apply suggestions from code review Co-authored-by: Sarah Rainsberger * Update packages/astro/src/core/errors/errors-data.ts Co-authored-by: Sarah Rainsberger * Misc punctuations fixes and additions to README Co-authored-by: Sarah Rainsberger --- .changeset/curly-experts-think.md | 5 + packages/astro/e2e/errors.test.js | 6 +- packages/astro/package.json | 4 +- packages/astro/src/cli/index.ts | 5 +- packages/astro/src/core/build/index.ts | 3 +- packages/astro/src/core/build/static-build.ts | 15 +- packages/astro/src/core/compile/compile.ts | 18 +- packages/astro/src/core/compile/style.ts | 10 +- packages/astro/src/core/config/config.ts | 18 +- packages/astro/src/core/endpoint/index.ts | 26 +-- packages/astro/src/core/errors/README.md | 57 +++++ packages/astro/src/core/errors/codes.ts | 24 --- packages/astro/src/core/errors/dev/utils.ts | 111 ++++++++-- packages/astro/src/core/errors/dev/vite.ts | 91 ++++---- packages/astro/src/core/errors/errors-data.ts | 199 ++++++++++++++++++ packages/astro/src/core/errors/errors.ts | 70 +++--- packages/astro/src/core/errors/index.ts | 13 +- packages/astro/src/core/errors/printer.ts | 5 +- packages/astro/src/core/errors/utils.ts | 68 ++---- packages/astro/src/core/messages.ts | 11 + packages/astro/src/core/render/core.ts | 22 +- packages/astro/src/core/render/result.ts | 29 +-- packages/astro/src/core/render/route-cache.ts | 41 ++-- .../astro/src/core/routing/manifest/create.ts | 5 - packages/astro/src/core/routing/params.ts | 4 +- packages/astro/src/core/routing/validation.ts | 88 +++++--- packages/astro/src/events/error.ts | 6 +- packages/astro/src/jsx/babel.ts | 11 +- .../astro/src/runtime/server/hydration.ts | 13 +- .../src/runtime/server/render/component.ts | 95 +++++---- .../astro/src/runtime/server/render/page.ts | 53 ++++- .../src/vite-plugin-astro-server/plugin.ts | 22 +- .../src/vite-plugin-astro-server/request.ts | 2 +- .../src/vite-plugin-astro-server/route.ts | 7 +- .../src/vite-plugin-markdown-legacy/index.ts | 6 +- .../astro/src/vite-plugin-markdown/index.ts | 6 +- packages/astro/test/events.test.js | 6 +- pnpm-lock.yaml | 4 +- 38 files changed, 797 insertions(+), 382 deletions(-) create mode 100644 .changeset/curly-experts-think.md create mode 100644 packages/astro/src/core/errors/README.md delete mode 100644 packages/astro/src/core/errors/codes.ts create mode 100644 packages/astro/src/core/errors/errors-data.ts diff --git a/.changeset/curly-experts-think.md b/.changeset/curly-experts-think.md new file mode 100644 index 000000000..8696703e7 --- /dev/null +++ b/.changeset/curly-experts-think.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Improved error messages descriptions and hints to be more informative diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js index 9742df49c..ee0b70e67 100644 --- a/packages/astro/e2e/errors.test.js +++ b/packages/astro/e2e/errors.test.js @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { testFactory, getErrorOverlayMessage } from './test-utils.js'; +import { getErrorOverlayMessage, testFactory } from './test-utils.js'; const test = testFactory({ root: './fixtures/errors/' }); @@ -38,7 +38,9 @@ test.describe('Error display', () => { await page.goto(astro.resolveUrl('/import-not-found')); const message = await getErrorOverlayMessage(page); - expect(message).toMatch('Could not import "../abc.astro"'); + expect(message).toMatch( + 'Could not import "../abc.astro".\n\nThis is often caused by a typo in the import path. Please make sure the file exists.' + ); await Promise.all([ // Wait for page reload diff --git a/packages/astro/package.json b/packages/astro/package.json index 4d9868492..21e51c909 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -123,7 +123,6 @@ "debug": "^4.3.4", "deepmerge-ts": "^4.2.2", "diff": "^5.1.0", - "eol": "^0.9.1", "es-module-lexer": "^0.10.5", "esbuild": "^0.14.43", "execa": "^6.1.0", @@ -199,7 +198,8 @@ "remark-code-titles": "^0.1.2", "sass": "^1.52.2", "srcset-parse": "^1.1.0", - "unified": "^10.1.2" + "unified": "^10.1.2", + "eol": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.12.0", diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 93db99cb2..ddd4acc83 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -1,6 +1,5 @@ /* eslint-disable no-console */ import * as colors from 'kleur/colors'; -import { pathToFileURL } from 'url'; import type { Arguments as Flags } from 'yargs-parser'; import yargs from 'yargs-parser'; import { z } from 'zod'; @@ -94,9 +93,7 @@ async function handleConfigError( if (path) { error(logging, 'astro', `Unable to load ${colors.bold(path)}\n`); } - console.error( - formatErrorMessage(collectErrorMetadata(e, path ? pathToFileURL(path) : undefined)) + '\n' - ); + console.error(formatErrorMessage(collectErrorMetadata(e)) + '\n'); } } diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index e4b93d9c1..34f84b40f 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -13,7 +13,6 @@ import { runHookConfigSetup, } from '../../integrations/index.js'; import { createVite } from '../create-vite.js'; -import { enhanceViteSSRError } from '../errors/dev/index.js'; import { debug, info, levels, timerMessage } from '../logger/core.js'; import { apply as applyPolyfill } from '../polyfill.js'; import { RouteCache } from '../render/route-cache.js'; @@ -169,7 +168,7 @@ class AstroBuilder { try { await this.build(setupData); } catch (_err) { - throw enhanceViteSSRError(_err as Error); + throw _err; } } diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 79a746c7d..78c14973f 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -10,6 +10,7 @@ import { prependForwardSlash } from '../../core/path.js'; import { isModeServerWithNoAdapter } from '../../core/util.js'; import { runHookBuildSetup } from '../../integrations/index.js'; import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; +import { AstroError, AstroErrorData } from '../errors/index.js'; import { info } from '../logger/core.js'; import { getOutDirWithinCwd } from './common.js'; import { generatePages } from './generate.js'; @@ -26,19 +27,9 @@ import { injectManifest, vitePluginSSR } from './vite-plugin-ssr.js'; export async function staticBuild(opts: StaticBuildOptions) { const { allPages, settings } = opts; - // Verify this app is buildable. + // Make sure we have an adapter before building if (isModeServerWithNoAdapter(opts.settings)) { - throw new Error(`Cannot use \`output: 'server'\` without an adapter. -Install and configure the appropriate server adapter for your final deployment. -Learn more: https://docs.astro.build/en/guides/server-side-rendering/ - - // Example: astro.config.js - import netlify from '@astrojs/netlify'; - export default { - output: 'server', - adapter: netlify(), - } -`); + throw new AstroError(AstroErrorData.NoAdapterInstalled); } // The pages to be built for rendering purposes. diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 0b5a8f30c..716c45e86 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -3,8 +3,8 @@ import type { ResolvedConfig } from 'vite'; import type { AstroConfig } from '../../@types/astro'; import { transform } from '@astrojs/compiler'; -import { AstroErrorCodes } from '../errors/codes.js'; import { AggregateError, AstroError, CompilerError } from '../errors/errors.js'; +import { AstroErrorData } from '../errors/index.js'; import { prependForwardSlash } from '../path.js'; import { resolvePath, viteID } from '../util.js'; import { createStylePreprocessor } from './style.js'; @@ -61,7 +61,7 @@ async function compile({ // The compiler should be able to handle errors by itself, however // for the rare cases where it can't let's directly throw here with as much info as possible throw new CompilerError({ - errorCode: AstroErrorCodes.UnknownCompilerError, + ...AstroErrorData.UnknownCompilerError, message: err.message ?? 'Unknown compiler error', stack: err.stack, location: { @@ -70,22 +70,18 @@ async function compile({ }); }) .then((result) => { - const compilerError = result.diagnostics.find( - // HACK: The compiler currently mistakenly returns the wrong severity for warnings, so we'll also filter by code - // https://github.com/withastro/compiler/issues/595 - (diag) => diag.severity === 1 && diag.code < 2000 - ); + const compilerError = result.diagnostics.find((diag) => diag.severity === 1); if (compilerError) { throw new CompilerError({ - errorCode: compilerError.code, + code: compilerError.code, message: compilerError.text, location: { line: compilerError.location.line, column: compilerError.location.column, file: compilerError.location.file, }, - hint: compilerError.hint ? compilerError.hint : undefined, + hint: compilerError.hint, }); } @@ -94,8 +90,8 @@ async function compile({ return result; case 1: { let error = cssTransformErrors[0]; - if (!error.errorCode) { - error.errorCode = AstroErrorCodes.UnknownCompilerCSSError; + if (!error.code) { + error.code = AstroErrorData.UnknownCSSError.code; } throw cssTransformErrors[0]; diff --git a/packages/astro/src/core/compile/style.ts b/packages/astro/src/core/compile/style.ts index 4d8485ac2..9f32ad3a6 100644 --- a/packages/astro/src/core/compile/style.ts +++ b/packages/astro/src/core/compile/style.ts @@ -1,9 +1,7 @@ import type { TransformOptions } from '@astrojs/compiler'; import fs from 'fs'; import { preprocessCSS, ResolvedConfig } from 'vite'; -import { AstroErrorCodes } from '../errors/codes.js'; -import { CSSError } from '../errors/errors.js'; -import { positionAt } from '../errors/index.js'; +import { AstroErrorData, CSSError, positionAt } from '../errors/index.js'; export function createStylePreprocessor({ filename, @@ -57,7 +55,7 @@ function enhanceCSSError(err: any, filename: string) { // Vite will handle creating the frame for us with proper line numbers, no need to create one return new CSSError({ - errorCode: AstroErrorCodes.CssSyntaxError, + ...AstroErrorData.CSSSyntaxError, message: err.reason, location: { file: filename, @@ -72,7 +70,7 @@ function enhanceCSSError(err: any, filename: string) { const errorLine = positionAt(styleTagBeginning, fileContent).line + (err.line ?? 0); return new CSSError({ - errorCode: AstroErrorCodes.CssUnknownError, + ...AstroErrorData.UnknownCSSError, message: err.message, location: { file: filename, @@ -88,7 +86,7 @@ function enhanceCSSError(err: any, filename: string) { errorPosition.line += 1; return new CSSError({ - errorCode: AstroErrorCodes.CssUnknownError, + code: AstroErrorData.UnknownCSSError.code, message: err.message, location: { file: filename, diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts index ea0725d76..dc4e7bcd9 100644 --- a/packages/astro/src/core/config/config.ts +++ b/packages/astro/src/core/config/config.ts @@ -12,6 +12,7 @@ import { mergeConfig as mergeViteConfig } from 'vite'; import { LogOptions } from '../logger/core.js'; import { arraify, isObject, isURL } from '../util.js'; import { createRelativeSchema } from './schema.js'; +import { AstroError, AstroErrorData } from '../errors/index.js'; load.use([loadTypeScript]); @@ -76,9 +77,10 @@ export async function validateConfig( } } if (legacyConfigKey) { - throw new Error( - `Legacy configuration detected: "${legacyConfigKey}".\nPlease update your configuration to the new format!\nSee https://astro.build/config for more information.` - ); + throw new AstroError({ + ...AstroErrorData.ConfigLegacyKey, + message: AstroErrorData.ConfigLegacyKey.message(legacyConfigKey), + }); } /* eslint-enable no-console */ @@ -171,7 +173,10 @@ export async function resolveConfigPath( return configPath; } catch (e) { if (e instanceof ProloadError && flags.config) { - throw new Error(`Unable to resolve --config "${flags.config}"! Does the file exist?`); + throw new AstroError({ + ...AstroErrorData.ConfigNotFound, + message: AstroErrorData.ConfigNotFound.message(flags.config), + }); } throw e; } @@ -251,7 +256,10 @@ async function tryLoadConfig( return config as TryLoadConfigResult; } catch (e) { if (e instanceof ProloadError && flags.config) { - throw new Error(`Unable to resolve --config "${flags.config}"! Does the file exist?`); + throw new AstroError({ + ...AstroErrorData.ConfigNotFound, + message: AstroErrorData.ConfigNotFound.message(flags.config), + }); } const configPath = await resolveConfigPath(configOptions); diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index 11d6cb664..e6d24802d 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -5,6 +5,7 @@ import { renderEndpoint } from '../../runtime/server/index.js'; import { ASTRO_VERSION } from '../constants.js'; import { AstroCookies, attachToResponse } from '../cookies/index.js'; import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js'; +import { AstroError, AstroErrorData } from '../errors/index.js'; const clientAddressSymbol = Symbol.for('astro.clientAddress'); @@ -52,13 +53,12 @@ function createAPIContext({ get clientAddress() { if (!(clientAddressSymbol in request)) { if (adapterName) { - throw new Error( - `clientAddress is not available in the ${adapterName} adapter. File an issue with the adapter to add support.` - ); + throw new AstroError({ + ...AstroErrorData.SSRClientAddressNotAvailableInAdapter, + message: AstroErrorData.SSRClientAddressNotAvailableInAdapter.message(adapterName), + }); } else { - throw new Error( - `clientAddress is not available in your environment. Ensure that you are using an SSR adapter that supports this feature.` - ); + throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable); } } @@ -82,9 +82,13 @@ export async function call( }); if (paramsAndPropsResp === GetParamsAndPropsError.NoMatchingStaticPath) { - throw new Error( - `[getStaticPath] route pattern matched, but no matching static path found. (${ctx.pathname})` - ); + throw new AstroError({ + ...AstroErrorData.NoMatchingStaticPathFound, + message: AstroErrorData.NoMatchingStaticPathFound.message(ctx.pathname), + hint: ctx.route?.component + ? AstroErrorData.NoMatchingStaticPathFound.hint([ctx.route?.component]) + : '', + }); } const [params, props] = paramsAndPropsResp; @@ -120,8 +124,6 @@ function isRedirect(statusCode: number) { export function throwIfRedirectNotAllowed(response: Response, config: AstroConfig) { if (config.output !== 'server' && isRedirect(response.status)) { - throw new Error( - `Redirects are only available when using output: 'server'. Update your Astro config if you need SSR features.` - ); + throw new AstroError(AstroErrorData.StaticRedirectNotAllowed); } } diff --git a/packages/astro/src/core/errors/README.md b/packages/astro/src/core/errors/README.md new file mode 100644 index 000000000..60d37bf68 --- /dev/null +++ b/packages/astro/src/core/errors/README.md @@ -0,0 +1,57 @@ +# Errors + +> Interested in the technical details? See the comments in [errors-data.ts.](./errors-data.ts) + +## Writing error messages for Astro + +### Tips + +**Choosing an Error Code** + +Choose any available error code in the appropriate range: +- 01xxx and 02xxx are reserved for compiler errors and warnings respectively +- 03xxx: Astro errors (your error most likely goes here!) +- 04xxx: CSS errors +- 05xxx: Vite errors +- 06xxx: Markdown errors +- 07xxx: Configuration errors +- 07xxx-98xxx <- Need to add a category? Add it here! +- 99xxx: Catch-alls for unknown errors + +As long as it is unique, the exact error code used is unimportant. For example, error 5005 and error 5006 don't necessarily have to be related, or follow any logical pattern. + +Users are not reading codes sequentially. They're much more likely to directly land on the error or search for a specific code. + +If you are unsure about which error code to choose, ask [Erika](https://github.com/Princesseuh)! + +**Error Code Format** +- Begin with **what happened** and **why** (ex: `Could not use {feature} because Server⁠-⁠side Rendering is not enabled`) +- Then, **describe the action the user should take** (ex: `Update your Astro config with `output: 'server'` to enable Server⁠-⁠side Rendering.`) +- A `hint` can be used for any additional info that might help the user (ex: a link to the documentation, or a common cause) + +**Error Code Writing Style** +- Technical jargon is mostly okay! But, most abbreviations should be avoided. If a developer is unfamiliar with a technical term, spelling it out in full allows them to look it up on the web more easily. +- Describe the what, why and action to take from the user's perspective. Assume they don't know Astro internals, and care only about how Astro is _used_ (ex: `You are missing...` vs `Astro/file cannot find...`) +- Avoid using cutesy language (ex: Oops!). This tone minimizes the significance of the error, which _is_ important to the developer. The developer may be frustrated and your error message shouldn't be making jokes about their struggles. Only include words and phrases that help the developer **interpret the error** and **fix the problem**. + +### CLI specifics: +- If the error happened **during an action that changes the state of the project** (ex: editing configuration, creating files), the error should **reassure the user** about the state of their project (ex: "Failed to update configuration. Your project has been restored to its previous state.") +- If an "error" happened because of a conscious user action (ex: pressing CTRL+C during a choice), it is okay to add more personality (ex: "Operation cancelled. See you later, astronaut!"). Do keep in mind the previous point however (ex: "Operation cancelled. No worries, your project folder has already been created") + +### Shape +- **Error codes and names are permanent**, and should never be changed, nor deleted. Users should always be able to find an error by searching, and this ensures a matching result. When an error is no longer relevant, it should be deprecated, not removed. +- Contextual information may be used to enhance the message or the hint. However, the error code itself should not be included in the message as it will already be shown as part of the the error. +- Do not prefix `message` and `hint` with descriptive words such as "Error:" or "Hint:" as it may lead to duplicated labels in the UI / CLI. + +### Always remember + +Error are a reactive strategy. They are the last line of defense against a mistake. + +Before adding a new error message, ask yourself, "Was there a way this situation could've been avoided in the first place?" (docs, editor tooling etc). + +**If you can prevent the error, you don't need an error message!** + +## Additional resources on writing good error messages + +- [When life gives you lemons, write better error messages](https://wix-ux.com/when-life-gives-you-lemons-write-better-error-messages-46c5223e1a2f) +- [RustConf 2020 - Bending the Curve: A Personal Tutor at Your Fingertips by Esteban Kuber](https://www.youtube.com/watch?v=Z6X7Ada0ugE) (part on error messages starts around 19:17) diff --git a/packages/astro/src/core/errors/codes.ts b/packages/astro/src/core/errors/codes.ts deleted file mode 100644 index 24270bed2..000000000 --- a/packages/astro/src/core/errors/codes.ts +++ /dev/null @@ -1,24 +0,0 @@ -export enum AstroErrorCodes { - // 1xxx are reserved for compiler errors - StaticRedirectNotAllowed = 2005, - UnavailableInSSR = 2006, - // Runtime errors - GenericRuntimeError = 3000, - // PostCSS errors - CssSyntaxError = 4000, - CssUnknownError = 4001, - // Vite SSR errors - FailedToLoadModuleSSR = 5000, - // Config Errors - ConfigError = 6000, - - // Markdown Errors - GenericMarkdownError = 7000, - MarkdownFrontmatterParseError = 7001, - - // General catch-alls for cases where we have zero information - UnknownCompilerError = 9000, - UnknownCompilerCSSError = 9001, - UnknownViteSSRError = 9002, - UnknownError = 9999, -} diff --git a/packages/astro/src/core/errors/dev/utils.ts b/packages/astro/src/core/errors/dev/utils.ts index 44aed1889..510e5fa51 100644 --- a/packages/astro/src/core/errors/dev/utils.ts +++ b/packages/astro/src/core/errors/dev/utils.ts @@ -1,9 +1,12 @@ import type { BuildResult } from 'esbuild'; import * as fs from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import stripAnsi from 'strip-ansi'; import type { SSRError } from '../../../@types/astro.js'; import { AggregateError, ErrorWithMetadata } from '../errors.js'; import { codeFrame } from '../printer.js'; -import { collectInfoFromStacktrace } from '../utils.js'; +import { normalizeLF } from '../utils.js'; export const incompatiblePackages = { 'react-spectrum': `@adobe/react-spectrum is not compatible with Vite's server-side rendering mode at the moment. You can still use React Spectrum from the client. Create an island React component and use the client:only directive. From there you can use React Spectrum.`, @@ -14,7 +17,7 @@ export const incompatPackageExp = new RegExp(`(${Object.keys(incompatiblePackage * Takes any error-like object and returns a standardized Error + metadata object. * Useful for consistent reporting regardless of where the error surfaced from. */ -export function collectErrorMetadata(e: any, filePath?: URL): ErrorWithMetadata { +export function collectErrorMetadata(e: any, rootFolder?: URL | undefined): ErrorWithMetadata { const err = AggregateError.is(e) ? (e.errors as SSRError[]) : [e as SSRError]; err.forEach((error) => { @@ -22,6 +25,11 @@ export function collectErrorMetadata(e: any, filePath?: URL): ErrorWithMetadata error = collectInfoFromStacktrace(e); } + + if (error.loc?.file && rootFolder && !error.loc.file.startsWith('/')) { + error.loc.file = join(fileURLToPath(rootFolder), error.loc.file); + } + // If we don't have a frame, but we have a location let's try making up a frame for it if (!error.frame && error.loc) { try { @@ -32,21 +40,33 @@ export function collectErrorMetadata(e: any, filePath?: URL): ErrorWithMetadata } // Generic error (probably from Vite, and already formatted) - if (!error.hint) { - error.hint = generateHint(e, filePath); - } + error.hint = generateHint(e); }); // If we received an array of errors and it's not from us, it should be from ESBuild, try to extract info for Vite to display if (!AggregateError.is(e) && Array.isArray((e as any).errors)) { (e as BuildResult).errors.forEach((buildError, i) => { - const { location, pluginName } = buildError; + const { location, pluginName, text } = buildError; + + // ESBuild can give us a slightly better error message than the one in the error, so let's use it + err[i].message = text; if (location) { err[i].loc = { file: location.file, line: location.line, column: location.column }; err[i].id = err[0].id || location?.file; } + // Vite adds the error message to the frame for ESBuild errors, we don't want that + if (err[i].frame) { + const errorLines = err[i].frame?.trim().split('\n'); + + if (errorLines) { + err[i].frame = !/^\d/.test(errorLines[0]) + ? errorLines?.slice(1).join('\n') + : err[i].frame; + } + } + const possibleFilePath = err[i].pluginCode || err[i].id || location?.file; if (possibleFilePath && !err[i].frame) { try { @@ -61,7 +81,7 @@ export function collectErrorMetadata(e: any, filePath?: URL): ErrorWithMetadata err[i].plugin = pluginName; } - err[i].hint = generateHint(err[0], filePath); + err[i].hint = generateHint(err[0]); }); } @@ -69,14 +89,21 @@ export function collectErrorMetadata(e: any, filePath?: URL): ErrorWithMetadata return err[0]; } -function generateHint(err: ErrorWithMetadata, filePath?: URL): string | undefined { +function generateHint(err: ErrorWithMetadata): string | undefined { if (/Unknown file extension \"\.(jsx|vue|svelte|astro|css)\" for /.test(err.message)) { return 'You likely need to add this package to `vite.ssr.noExternal` in your astro config file.'; - } else if ( - err.toString().startsWith('ReferenceError') && - (err.loc?.file ?? filePath?.pathname)?.endsWith('.astro') - ) { - return 'export statements in `.astro` files do not have access to local variable declarations, only imported values.'; + } else if (err.toString().includes('document')) { + const hint = `Browser APIs are not available on the server. + +${ + err.loc?.file?.endsWith('.astro') + ? 'Move your code to a