Skip to content

Commit 6f37605

Browse files
committed
feat: add Nitro plugin to set Shopify-required response headers for document requests
1 parent 7b08195 commit 6f37605

2 files changed

Lines changed: 49 additions & 5 deletions

File tree

src/module.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,6 @@ export default defineNuxtModule<ModuleOptions>({
8585
nuxt.hook('app:resolve', () => {
8686
nuxt.options.app.head = nuxt.options.app.head || {}
8787
nuxt.options.app.head.meta = [
88-
{
89-
name: 'content-security-policy',
90-
content:
91-
"frame-ancestors 'self' *.myshopify.com *.shopify.com *.trycloudflare.com"
92-
},
9388
...(nuxt.options.app.head.meta || [])
9489
]
9590
nuxt.options.app.head.script = [
@@ -201,6 +196,9 @@ export {}
201196
nitroConfig.plugins.push(
202197
resolver.resolve('./runtime/server/plugins/shopify-defaults')
203198
)
199+
nitroConfig.plugins.push(
200+
resolver.resolve('./runtime/server/plugins/add-response-headers')
201+
)
204202
})
205203

206204
// ─── Transpile ─────────────────────────────────────────────────────
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { defineNitroPlugin } from 'nitropack/runtime'
2+
import { getQuery, setResponseHeader } from 'h3'
3+
4+
const APP_BRIDGE_URL = 'https://cdn.shopify.com/shopifycloud/app-bridge.js'
5+
const POLARIS_URL = 'https://cdn.shopify.com/shopifycloud/polaris.js'
6+
const CDN_URL = 'https://cdn.shopify.com'
7+
8+
/**
9+
* Nitro plugin that adds Shopify-required response headers to every document request.
10+
*
11+
* Mirrors the behaviour of Shopify's official `addDocumentResponseHeaders`:
12+
* - `Content-Security-Policy`: frame-ancestors scoped to the requesting shop
13+
* - `Link`: preconnect / preload hints for App Bridge + Polaris CDN assets
14+
*
15+
* @see https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-app-react-router/src/server/authenticate/helpers/add-response-headers.ts
16+
*/
17+
export default defineNitroPlugin((nitroApp) => {
18+
nitroApp.hooks.hook('request', (event) => {
19+
const query = getQuery(event)
20+
const shop = typeof query.shop === 'string' ? query.shop : undefined
21+
22+
// Link header — preconnect to CDN + preload App Bridge & Polaris scripts
23+
if (shop) {
24+
setResponseHeader(
25+
event,
26+
'Link',
27+
`<${CDN_URL}>; rel="preconnect", <${APP_BRIDGE_URL}>; rel="preload"; as="script", <${POLARIS_URL}>; rel="preload"; as="script"`
28+
)
29+
}
30+
31+
// Content-Security-Policy — scope frame-ancestors to the requesting shop
32+
if (shop) {
33+
setResponseHeader(
34+
event,
35+
'Content-Security-Policy',
36+
`frame-ancestors https://${shop} https://admin.shopify.com https://*.spin.dev https://admin.myshopify.io https://admin.shop.dev;`
37+
)
38+
} else {
39+
setResponseHeader(
40+
event,
41+
'Content-Security-Policy',
42+
`frame-ancestors https://*.myshopify.com https://admin.shopify.com https://*.spin.dev https://admin.myshopify.io https://admin.shop.dev;`
43+
)
44+
}
45+
})
46+
})

0 commit comments

Comments
 (0)