diff --git a/docs/content/3.config/index.md b/docs/content/3.config/index.md index 295ecd612c..c856457ba0 100644 --- a/docs/content/3.config/index.md +++ b/docs/content/3.config/index.md @@ -40,7 +40,6 @@ Server runtime configuration. **Note:**: `nitro` namespace is reserved. - ## `experimental` @@ -59,7 +58,10 @@ Storage configuration. - Default: `false` -Enable timing information. +Enable timing information: + +- Nitro startup time log +- `Server-Timing` header on HTTP responses ## `renderer` @@ -171,8 +173,6 @@ An array of paths to nitro plugins. They will be executed by order on the first A map from dynamic virtual import names to their contents or an (async) function that returns it. - - ## `baseURL` @@ -242,7 +242,6 @@ Route options. It is a map from route pattern (following [unjs/radix3](https://g When `cache` option is set, handlers matching pattern will be automatically wrapped with `defineCachedEventHandler`. See [Cache API](/guide/introduction/cache) for all available cache options. (`swr: true|number` is shortcut for `cache: { swr: true, maxAge: number }`.) - **Example:** ```js @@ -267,8 +266,6 @@ Prerendered options. Any route specified will be fetched during the build and co If `crawlLinks` option is set to `true`, nitro starts with `/` by default (or all routes in `routes` array) and for HTML pages extracts `` tags and prerender them as well. - - ## `rootDir` @@ -297,8 +294,6 @@ nitro's temporary working directory for generating build-related files. Output directories for production bundle. - - ## `dev` @@ -335,7 +330,6 @@ Preview and deploy command hints are usually filled by deployment presets. A custom error handler function for development errors. - ## `rollupConfig` diff --git a/src/nitro.ts b/src/nitro.ts index 773a390b0b..9414efee82 100644 --- a/src/nitro.ts +++ b/src/nitro.ts @@ -34,6 +34,10 @@ export async function createNitro(config: NitroConfig = {}): Promise { nitro.options.plugins.push("#internal/nitro/debug"); } + if (nitro.options.timing) { + nitro.options.plugins.push("#internal/nitro/timing"); + } + // Logger config if (nitro.options.logLevel !== undefined) { nitro.logger.level = nitro.options.logLevel; diff --git a/src/runtime/app.ts b/src/runtime/app.ts index cfda794306..08cd7d1fe1 100644 --- a/src/runtime/app.ts +++ b/src/runtime/app.ts @@ -14,7 +14,6 @@ import { } from "unenv/runtime/fetch/index"; import { createHooks, Hookable } from "hookable"; import { useRuntimeConfig } from "./config"; -import { timingMiddleware } from "./timing"; import { cachedEventHandler } from "./cache"; import { createRouteRulesHandler, getRouteRulesForPath } from "./route-rules"; import { plugins } from "#internal/nitro/virtual/plugins"; @@ -40,8 +39,6 @@ function createNitroApp(): NitroApp { onError: errorHandler, }); - h3App.use(config.app.baseURL, timingMiddleware); - const router = createRouter(); h3App.use(createRouteRulesHandler()); diff --git a/src/runtime/timing.ts b/src/runtime/timing.ts index 40655425fe..94467c4515 100644 --- a/src/runtime/timing.ts +++ b/src/runtime/timing.ts @@ -1,13 +1,15 @@ import { eventHandler } from "h3"; -export const globalTiming = globalThis.__timing__ || { +import { defineNitroPlugin } from "./plugin"; + +const globalTiming = globalThis.__timing__ || { start: () => 0, end: () => 0, metrics: [], }; // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing -export const timingMiddleware = eventHandler((event) => { +const timingMiddleware = eventHandler((event) => { const start = globalTiming.start(); const _end = event.node.res.end; @@ -30,3 +32,11 @@ export const timingMiddleware = eventHandler((event) => { return this; }.bind(event.node.res); }); + +export default defineNitroPlugin((nitro) => { + // Always add timing middleware to the beginning of handler stack + nitro.h3App.stack.unshift({ + route: "/", + handler: timingMiddleware, + }); +}); diff --git a/test/tests.ts b/test/tests.ts index eb0fcc09ed..286eb14c53 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -57,6 +57,7 @@ export async function setupTest(preset: string) { "/rules/nested/**": { redirect: "/base", headers: { "x-test": "test" } }, "/rules/nested/override": { redirect: { to: "/other" } }, }, + timing: preset !== "cloudflare" && preset !== "vercel-edge", })); if (ctx.isDev) { @@ -240,4 +241,14 @@ export function testNitro( additionalTests(ctx, callHandler); } } + + if (ctx.nitro!.options.timing) { + it("set server timing header", async () => { + const { data, status, headers } = await callHandler({ + url: "/api/hello", + }); + expect(status).toBe(200); + expect(headers["server-timing"]).toMatch(/-;dur=\d+;desc="Generate"/); + }); + } }