diff --git a/packages/cli/package.json b/packages/cli/package.json index 946945b00c..5f9142ca90 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -20,6 +20,7 @@ "fsevents": "~2.3.2" }, "devDependencies": { + "@nuxt/design": "0.0.4", "@nuxt/kit": "^0.6.4", "@types/clear": "^0", "@types/debounce-promise": "^3", diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index e2fb99789c..dfcbbcb289 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -22,7 +22,7 @@ export async function invoke (args) { const load = async (isRestart) => { try { const message = `${isRestart ? 'Restarting' : 'Starting'} nuxt...` - server.setApp(createLoadingHandler(message, 1)) + server.setApp(createLoadingHandler(message)) if (isRestart) { console.log(message) } diff --git a/packages/cli/src/utils/server.ts b/packages/cli/src/utils/server.ts index 50098e0cb7..bed996b5da 100644 --- a/packages/cli/src/utils/server.ts +++ b/packages/cli/src/utils/server.ts @@ -1,7 +1,8 @@ import type { RequestListener } from 'http' +import { loading } from '@nuxt/design' export function createServer () { - const listener = createDynamicFunction(createLoadingHandler('Loading...', 1)) + const listener = createDynamicFunction(createLoadingHandler('Loading...')) async function listen (opts) { const { listen } = await import('listhen') @@ -14,12 +15,11 @@ export function createServer () { } } -export function createLoadingHandler (message: string, retryAfter = 60): RequestListener { +export function createLoadingHandler (message: string): RequestListener { return (_req, res) => { res.setHeader('Content-Type', 'text/html; charset=UTF-8') res.statusCode = 503 /* Service Unavailable */ - res.setHeader('Retry-After', retryAfter) - res.end(`${message}`) + res.end(loading({ loading: message })) } } diff --git a/packages/nitro/package.json b/packages/nitro/package.json index 6d56fdfa6a..fed8a1c899 100644 --- a/packages/nitro/package.json +++ b/packages/nitro/package.json @@ -15,6 +15,7 @@ "dependencies": { "@cloudflare/kv-asset-handler": "^0.1.3", "@netlify/functions": "^0.7.2", + "@nuxt/design": "0.0.4", "@nuxt/devalue": "^2.0.0", "@nuxt/kit": "^0.6.4", "@rollup/plugin-alias": "^3.1.2", diff --git a/packages/nitro/src/runtime/app/render.ts b/packages/nitro/src/runtime/app/render.ts index 6756ca18f2..7f666789ab 100644 --- a/packages/nitro/src/runtime/app/render.ts +++ b/packages/nitro/src/runtime/app/render.ts @@ -47,6 +47,11 @@ export async function renderMiddleware (req, res) { const renderer = await loadRenderer() const rendered = await renderer.renderToString(ssrContext) + // Handle errors + if (ssrContext.error) { + throw ssrContext.error + } + if (ssrContext.nuxt.hooks) { await ssrContext.nuxt.hooks.callHook('app:rendered') } diff --git a/packages/nitro/src/runtime/server/error.ts b/packages/nitro/src/runtime/server/error.ts index 155325d125..e1b98f984d 100644 --- a/packages/nitro/src/runtime/server/error.ts +++ b/packages/nitro/src/runtime/server/error.ts @@ -1,8 +1,15 @@ // import ansiHTML from 'ansi-html' +import type { IncomingMessage, ServerResponse } from 'http' +import { error500, error404, errorDev } from '@nuxt/design' const cwd = process.cwd() -// TODO: Handle process.env.DEBUG -export function handleError (error, req, res) { +const hasReqHeader = (req, header, includes) => req.headers[header] && req.headers[header].toLowerCase().includes(includes) + +const isDev = process.env.NODE_ENV === 'development' + +export function handleError (error, req: IncomingMessage, res: ServerResponse) { + const isJsonRequest = hasReqHeader(req, 'accept', 'application/json') || hasReqHeader(req, 'user-agent', 'curl/') || hasReqHeader(req, 'user-agent', 'httpie/') + const stack = (error.stack || '') .split('\n') .splice(1) @@ -21,47 +28,35 @@ export function handleError (error, req, res) { } }) - console.error(error.message + '\n' + stack.map(l => ' ' + l.text).join(' \n')) + const is404 = error.statusCode === 404 - const html = ` - - - - - Nuxt Error - - - -
-
${req.method} ${req.url}

-

${error.toString()}

-
${stack.map(i =>
-        `${i.text}`
-  ).join('\n')
-    }
-
- - -` + const errorObject = { + statusCode: error.statusCode || 500, + statusMessage: is404 ? 'Page Not Found' : 'Internal Server Error', + description: isDev && !is404 + ? ` +

${error.message}

+
${stack.map(i => `${i.text}`).join('\n')}
+ ` + : '' + } res.statusCode = error.statusCode || 500 - res.statusMessage = error.statusMessage || 'Internal Error' + res.statusMessage = error.statusMessage || 'Internal Server Error' + + // Console output + if (!is404) { + console.error(error.message + '\n' + stack.map(l => ' ' + l.text).join(' \n')) + } + + // JSON response + if (isJsonRequest) { + res.setHeader('Content-Type', 'application/json') + return res.end(JSON.stringify(errorObject)) + } + + // HTML response + const errorTemplate = is404 ? error404 : (isDev ? errorDev : error500) + const html = errorTemplate(errorObject) res.end(html) } diff --git a/packages/nitro/src/server/dev.ts b/packages/nitro/src/server/dev.ts index 570bf1c127..2b856a54eb 100644 --- a/packages/nitro/src/server/dev.ts +++ b/packages/nitro/src/server/dev.ts @@ -1,5 +1,6 @@ import { Worker } from 'worker_threads' +import { loading as loadingTemplate } from '@nuxt/design' import chokidar, { FSWatcher } from 'chokidar' import debounce from 'debounce' import { stat } from 'fs-extra' @@ -74,7 +75,7 @@ export function createDevServer (nitroContext: NitroContext) { }) } else { res.setHeader('Content-Type', 'text/html; charset=UTF-8') - res.end('...') + res.end(loadingTemplate({})) } }) diff --git a/packages/pages/src/module.ts b/packages/pages/src/module.ts index 153f33380b..5bb7f518be 100644 --- a/packages/pages/src/module.ts +++ b/packages/pages/src/module.ts @@ -36,17 +36,6 @@ export default defineNuxtModule({ // Resolve routes const routes = await resolvePagesRoutes(nuxt) - // Add 404 page is not added - const page404 = routes.find(route => route.name === '404') - if (!page404) { - routes.push({ - name: '404', - path: '/:catchAll(.*)*', - file: resolve(runtimeDir, '404.vue'), - children: [] - }) - } - // Add routes.js app.templates.push({ path: 'routes.js', diff --git a/packages/pages/src/runtime/404.vue b/packages/pages/src/runtime/404.vue deleted file mode 100644 index dd196f5823..0000000000 --- a/packages/pages/src/runtime/404.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/packages/pages/src/runtime/page.vue b/packages/pages/src/runtime/page.vue index 8f34d4400d..6e6d8a441a 100644 --- a/packages/pages/src/runtime/page.vue +++ b/packages/pages/src/runtime/page.vue @@ -1,6 +1,6 @@ diff --git a/packages/pages/src/runtime/router.ts b/packages/pages/src/runtime/router.ts index 396a6678a2..87474f12d5 100644 --- a/packages/pages/src/runtime/router.ts +++ b/packages/pages/src/runtime/router.ts @@ -43,13 +43,15 @@ export default defineNuxtPlugin((nuxt) => { if (process.server) { router.push(nuxt.ssrContext.url) } - try { - await router.isReady() - if (!router.currentRoute.value.matched.length) { - // TODO - } - } catch (err) { - // TODO + + await router.isReady() + + const is404 = router.currentRoute.value.matched.length === 0 + if (process.server && is404) { + const error = new Error(`Page not found: ${nuxt.ssrContext.url}`) + // @ts-ignore + error.statusCode = 404 + nuxt.ssrContext.error = error } }) }) diff --git a/yarn.lock b/yarn.lock index 6e8f36c36a..ec5643fba2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1305,6 +1305,13 @@ __metadata: languageName: unknown linkType: soft +"@nuxt/design@npm:0.0.4": + version: 0.0.4 + resolution: "@nuxt/design@npm:0.0.4" + checksum: c28506ff41e26721dc1f3a3101d9d2824b3b44e17b4abf1ab246bc2777a49d743fc42b8b1e056460934461892b3af28c4ee96524a1c61d099ad02c23fc22f115 + languageName: node + linkType: hard + "@nuxt/devalue@npm:^2.0.0": version: 2.0.0 resolution: "@nuxt/devalue@npm:2.0.0" @@ -1354,6 +1361,7 @@ __metadata: dependencies: "@cloudflare/kv-asset-handler": ^0.1.3 "@netlify/functions": ^0.7.2 + "@nuxt/design": 0.0.4 "@nuxt/devalue": ^2.0.0 "@nuxt/kit": ^0.6.4 "@rollup/plugin-alias": ^3.1.2 @@ -8531,6 +8539,7 @@ fsevents@~2.3.2: version: 0.0.0-use.local resolution: "nuxt-cli@workspace:packages/cli" dependencies: + "@nuxt/design": 0.0.4 "@nuxt/kit": ^0.6.4 "@types/clear": ^0 "@types/debounce-promise": ^3