From 613340edcfbefd47bcd31806c2d4eb30d3d4d465 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Tue, 8 Oct 2024 18:16:31 +0200 Subject: [PATCH] feat(nuxt): add `useResponseHeader` composable (#27131) --- .../2.composables/use-response-header.md | 48 +++++++++++++++++++ packages/nuxt/src/app/composables/index.ts | 2 +- packages/nuxt/src/app/composables/ssr.ts | 32 ++++++++++++- packages/nuxt/src/app/index.ts | 2 +- packages/nuxt/src/imports/presets.ts | 2 +- test/nuxt/composables.test.ts | 4 +- 6 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 docs/3.api/2.composables/use-response-header.md diff --git a/docs/3.api/2.composables/use-response-header.md b/docs/3.api/2.composables/use-response-header.md new file mode 100644 index 0000000000..d78fd89a4a --- /dev/null +++ b/docs/3.api/2.composables/use-response-header.md @@ -0,0 +1,48 @@ +--- +title: "useResponseHeader" +description: "Use useResponseHeader to set a server response header." +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/ssr.ts + size: xs +--- + +::important +This composable is available in Nuxt v3.14+. +:: + +You can use the built-in [`useResponseHeader`](/docs/api/composables/use-response-header) composable to set any server response header within your pages, components, and plugins. + +```ts +// Set the a custom response header +const header = useResponseHeader('X-My-Header'); +header.value = 'my-value'; +``` + +## Example + +We can use `useResponseHeader` to easily set a response header on a per-page basis. + +```vue [pages/test.vue] + + + +``` + +We can use `useResponseHeader` for example in Nuxt [middleware](/docs/guide/directory-structure/middleware) to set a response header for all pages. + +```ts [middleware/my-header-middleware.ts] +export default defineNuxtRouteMiddleware((to, from) => { + const header = useResponseHeader('X-My-Always-Header'); + header.value = `I'm Always here!`; +}); + +``` diff --git a/packages/nuxt/src/app/composables/index.ts b/packages/nuxt/src/app/composables/index.ts index c0abdec2e4..05ba560d09 100644 --- a/packages/nuxt/src/app/composables/index.ts +++ b/packages/nuxt/src/app/composables/index.ts @@ -24,7 +24,7 @@ export { useFetch, useLazyFetch } from './fetch' export type { FetchResult, UseFetchOptions } from './fetch' export { useCookie, refreshCookie } from './cookie' export type { CookieOptions, CookieRef } from './cookie' -export { onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus } from './ssr' +export { onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader } from './ssr' export { onNuxtReady } from './ready' export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router' export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router' diff --git a/packages/nuxt/src/app/composables/ssr.ts b/packages/nuxt/src/app/composables/ssr.ts index 56f3383109..59db9bd327 100644 --- a/packages/nuxt/src/app/composables/ssr.ts +++ b/packages/nuxt/src/app/composables/ssr.ts @@ -1,6 +1,6 @@ import type { H3Event } from 'h3' -import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders } from 'h3' -import { getCurrentInstance } from 'vue' +import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders, getResponseHeader, removeResponseHeader, setResponseHeader } from 'h3' +import { computed, getCurrentInstance, ref } from 'vue' import { useServerHead } from '@unhead/vue' import type { NuxtApp } from '../nuxt' @@ -61,6 +61,34 @@ export function setResponseStatus (arg1: H3Event | number | undefined, arg2?: nu } } +/** @since 3.14.0 */ +export function useResponseHeader (header: string) { + if (import.meta.client) { + if (import.meta.dev) { + return computed({ + get: () => undefined, + set: () => console.warn('[nuxt] Setting response headers is not supported in the browser.'), + }) + } + return ref() + } + + const event = useRequestEvent()! + + return computed({ + get () { + return getResponseHeader(event, header) + }, + set (newValue) { + if (!newValue) { + return removeResponseHeader(event, header) + } + + return setResponseHeader(event, header, newValue) + }, + }) +} + /** @since 3.8.0 */ export function prerenderRoutes (path: string | string[]) { if (!import.meta.server || !import.meta.prerender) { return } diff --git a/packages/nuxt/src/app/index.ts b/packages/nuxt/src/app/index.ts index 363c72d1bf..43fcc404e1 100644 --- a/packages/nuxt/src/app/index.ts +++ b/packages/nuxt/src/app/index.ts @@ -1,7 +1,7 @@ export { applyPlugin, applyPlugins, callWithNuxt, createNuxtApp, defineAppConfig, defineNuxtPlugin, definePayloadPlugin, isNuxtPlugin, registerPluginHooks, tryUseNuxtApp, useNuxtApp, useRuntimeConfig } from './nuxt' export type { CreateOptions, NuxtApp, NuxtPayload, NuxtPluginIndicator, NuxtSSRContext, ObjectPlugin, Plugin, PluginEnvContext, PluginMeta, ResolvedPluginMeta, RuntimeNuxtHooks } from './nuxt' -export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index' +export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index' export type { AddRouteMiddlewareOptions, AsyncData, AsyncDataOptions, AsyncDataRequestStatus, CookieOptions, CookieRef, FetchResult, NuxtAppManifest, NuxtAppManifestMeta, NuxtError, ReloadNuxtAppOptions, RouteMiddleware, UseFetchOptions } from './composables/index' export { defineNuxtLink } from './components/index' diff --git a/packages/nuxt/src/imports/presets.ts b/packages/nuxt/src/imports/presets.ts index 70be02d738..995e6cb8bf 100644 --- a/packages/nuxt/src/imports/presets.ts +++ b/packages/nuxt/src/imports/presets.ts @@ -66,7 +66,7 @@ const granularAppPresets: InlinePreset[] = [ from: '#app/composables/cookie', }, { - imports: ['onPrehydrate', 'prerenderRoutes', 'useRequestHeader', 'useRequestHeaders', 'useRequestEvent', 'useRequestFetch', 'setResponseStatus'], + imports: ['onPrehydrate', 'prerenderRoutes', 'useRequestHeader', 'useRequestHeaders', 'useResponseHeader', 'useRequestEvent', 'useRequestFetch', 'setResponseStatus'], from: '#app/composables/ssr', }, { diff --git a/test/nuxt/composables.test.ts b/test/nuxt/composables.test.ts index 6b3b750244..5ed08efd5a 100644 --- a/test/nuxt/composables.test.ts +++ b/test/nuxt/composables.test.ts @@ -12,7 +12,7 @@ import * as composables from '#app/composables' import { clearNuxtData, refreshNuxtData, useAsyncData, useNuxtData } from '#app/composables/asyncData' import { clearError, createError, isNuxtError, showError, useError } from '#app/composables/error' import { onNuxtReady } from '#app/composables/ready' -import { setResponseStatus, useRequestEvent, useRequestFetch, useRequestHeaders } from '#app/composables/ssr' +import { setResponseStatus, useRequestEvent, useRequestFetch, useRequestHeaders, useResponseHeader } from '#app/composables/ssr' import { clearNuxtState, useState } from '#app/composables/state' import { useRequestURL } from '#app/composables/url' import { getAppManifest, getRouteRules } from '#app/composables/manifest' @@ -85,6 +85,7 @@ describe('composables', () => { 'useRequestFetch', 'isPrerendered', 'useRequestHeaders', + 'useResponseHeader', 'useCookie', 'clearNuxtState', 'useState', @@ -397,6 +398,7 @@ describe('ssr composables', () => { expect(useRequestFetch()).toEqual($fetch) expect(useRequestHeaders()).toEqual({}) expect(prerenderRoutes('/')).toBeUndefined() + expect(useResponseHeader('x-test').value).toBeUndefined() }) })