Skip to content

Commit e5a4c3d

Browse files
cameron-martinwardpeet
authored andcommitted
feat(gatsby-plugin-netlify): Add caching headers for immutable assets (#14000)
1 parent 8955e19 commit e5a4c3d

File tree

4 files changed

+332
-5
lines changed

4 files changed

+332
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`with caching headers 1`] = `
4+
"## Created with gatsby-plugin-netlify
5+
6+
/*
7+
X-Frame-Options: DENY
8+
X-XSS-Protection: 1; mode=block
9+
X-Content-Type-Options: nosniff
10+
Referrer-Policy: same-origin
11+
/component---node-modules-gatsby-plugin-offline-app-shell-js-78f9e4dea04737fa062d.js
12+
Cache-Control: public, max-age=31536000, immutable
13+
/0-0180cd94ef2497ac7db8.js
14+
Cache-Control: public, max-age=31536000, immutable
15+
/component---src-templates-blog-post-js-517987eae96e75cddbe7.js
16+
Cache-Control: public, max-age=31536000, immutable
17+
/component---src-pages-404-js-53e6c51a5a7e73090f50.js
18+
Cache-Control: public, max-age=31536000, immutable
19+
/component---src-pages-index-js-0bdd01c77ee09ef0224c.js
20+
Cache-Control: public, max-age=31536000, immutable
21+
/pages-manifest-ab11f09e0ca7ecd3b43e.js
22+
Cache-Control: public, max-age=31536000, immutable
23+
/webpack-runtime-acaa8994f1f704475e21.js
24+
Cache-Control: public, max-age=31536000, immutable
25+
/styles.1025963f4f2ec7abbad4.css
26+
Cache-Control: public, max-age=31536000, immutable
27+
/styles-565f081c8374bbda155f.js
28+
Cache-Control: public, max-age=31536000, immutable
29+
/app-f33c13590352da20930f.js
30+
Cache-Control: public, max-age=31536000, immutable
31+
/static/*
32+
Cache-Control: public, max-age=31536000, immutable
33+
/sw.js
34+
Cache-Control: no-cache
35+
/offline-plugin-app-shell-fallback/
36+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
37+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
38+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
39+
Link: </component---node-modules-gatsby-plugin-offline-app-shell-js-78f9e4dea04737fa062d.js>; rel=preload; as=script
40+
/hi-folks/
41+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
42+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
43+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
44+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
45+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
46+
/my-second-post/
47+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
48+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
49+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
50+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
51+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
52+
/hello-world/
53+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
54+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
55+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
56+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
57+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
58+
/404/
59+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
60+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
61+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
62+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
63+
Link: </component---src-pages-404-js-53e6c51a5a7e73090f50.js>; rel=preload; as=script
64+
/
65+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
66+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
67+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
68+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
69+
Link: </component---src-pages-index-js-0bdd01c77ee09ef0224c.js>; rel=preload; as=script
70+
/404.html
71+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
72+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
73+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
74+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
75+
Link: </component---src-pages-404-js-53e6c51a5a7e73090f50.js>; rel=preload; as=script
76+
"
77+
`;
78+
79+
exports[`without caching headers 1`] = `
80+
"## Created with gatsby-plugin-netlify
81+
82+
/*
83+
X-Frame-Options: DENY
84+
X-XSS-Protection: 1; mode=block
85+
X-Content-Type-Options: nosniff
86+
Referrer-Policy: same-origin
87+
/offline-plugin-app-shell-fallback/
88+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
89+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
90+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
91+
Link: </component---node-modules-gatsby-plugin-offline-app-shell-js-78f9e4dea04737fa062d.js>; rel=preload; as=script
92+
/hi-folks/
93+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
94+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
95+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
96+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
97+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
98+
/my-second-post/
99+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
100+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
101+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
102+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
103+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
104+
/hello-world/
105+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
106+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
107+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
108+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
109+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
110+
/404/
111+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
112+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
113+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
114+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
115+
Link: </component---src-pages-404-js-53e6c51a5a7e73090f50.js>; rel=preload; as=script
116+
/
117+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
118+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
119+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
120+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
121+
Link: </component---src-pages-index-js-0bdd01c77ee09ef0224c.js>; rel=preload; as=script
122+
/404.html
123+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
124+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
125+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
126+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
127+
Link: </component---src-pages-404-js-53e6c51a5a7e73090f50.js>; rel=preload; as=script
128+
"
129+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import buildHeadersProgram from "../build-headers-program"
2+
import path from "path"
3+
import os from "os"
4+
import fs from "fs-extra"
5+
import { DEFAULT_OPTIONS } from "../constants"
6+
7+
const createPluginData = async () => {
8+
const tmpDir = await fs.mkdtemp(
9+
path.join(os.tmpdir(), `gatsby-plugin-netlify-`)
10+
)
11+
12+
return {
13+
pages: new Map([
14+
[
15+
`/offline-plugin-app-shell-fallback/`,
16+
{
17+
jsonName: `offline-plugin-app-shell-fallback-a30`,
18+
internalComponentName: `ComponentOfflinePluginAppShellFallback`,
19+
path: `/offline-plugin-app-shell-fallback/`,
20+
matchPath: undefined,
21+
componentChunkName: `component---node-modules-gatsby-plugin-offline-app-shell-js`,
22+
isCreatedByStatefulCreatePages: false,
23+
context: {},
24+
updatedAt: 1557740602268,
25+
pluginCreator___NODE: `63e5f7ff-e5f1-58f7-8e2c-55872ac42281`,
26+
pluginCreatorId: `63e5f7ff-e5f1-58f7-8e2c-55872ac42281`,
27+
},
28+
],
29+
[
30+
`/hi-folks/`,
31+
{
32+
jsonName: `hi-folks-a2b`,
33+
internalComponentName: `ComponentHiFolks`,
34+
path: `/hi-folks/`,
35+
matchPath: undefined,
36+
componentChunkName: `component---src-templates-blog-post-js`,
37+
isCreatedByStatefulCreatePages: false,
38+
context: {},
39+
updatedAt: 1557740602330,
40+
pluginCreator___NODE: `7374ebf2-d961-52ee-92a2-c25e7cb387a9`,
41+
pluginCreatorId: `7374ebf2-d961-52ee-92a2-c25e7cb387a9`,
42+
},
43+
],
44+
[
45+
`/my-second-post/`,
46+
{
47+
jsonName: `my-second-post-2aa`,
48+
internalComponentName: `ComponentMySecondPost`,
49+
path: `/my-second-post/`,
50+
matchPath: undefined,
51+
componentChunkName: `component---src-templates-blog-post-js`,
52+
isCreatedByStatefulCreatePages: false,
53+
context: {},
54+
updatedAt: 1557740602333,
55+
pluginCreator___NODE: `7374ebf2-d961-52ee-92a2-c25e7cb387a9`,
56+
pluginCreatorId: `7374ebf2-d961-52ee-92a2-c25e7cb387a9`,
57+
},
58+
],
59+
[
60+
`/hello-world/`,
61+
{
62+
jsonName: `hello-world-8bc`,
63+
internalComponentName: `ComponentHelloWorld`,
64+
path: `/hello-world/`,
65+
matchPath: undefined,
66+
componentChunkName: `component---src-templates-blog-post-js`,
67+
isCreatedByStatefulCreatePages: false,
68+
context: {},
69+
updatedAt: 1557740602335,
70+
pluginCreator___NODE: `7374ebf2-d961-52ee-92a2-c25e7cb387a9`,
71+
pluginCreatorId: `7374ebf2-d961-52ee-92a2-c25e7cb387a9`,
72+
},
73+
],
74+
[
75+
`/404/`,
76+
{
77+
jsonName: `404-22d`,
78+
internalComponentName: `Component404`,
79+
path: `/404/`,
80+
matchPath: undefined,
81+
componentChunkName: `component---src-pages-404-js`,
82+
isCreatedByStatefulCreatePages: true,
83+
context: {},
84+
updatedAt: 1557740602358,
85+
pluginCreator___NODE: `049c1cfd-95f7-5555-a4ac-9b396d098b26`,
86+
pluginCreatorId: `049c1cfd-95f7-5555-a4ac-9b396d098b26`,
87+
},
88+
],
89+
[
90+
`/`,
91+
{
92+
jsonName: `index`,
93+
internalComponentName: `ComponentIndex`,
94+
path: `/`,
95+
matchPath: undefined,
96+
componentChunkName: `component---src-pages-index-js`,
97+
isCreatedByStatefulCreatePages: true,
98+
context: {},
99+
updatedAt: 1557740602361,
100+
pluginCreator___NODE: `049c1cfd-95f7-5555-a4ac-9b396d098b26`,
101+
pluginCreatorId: `049c1cfd-95f7-5555-a4ac-9b396d098b26`,
102+
},
103+
],
104+
[
105+
`/404.html`,
106+
{
107+
jsonName: `404-html-516`,
108+
internalComponentName: `Component404Html`,
109+
path: `/404.html`,
110+
matchPath: undefined,
111+
componentChunkName: `component---src-pages-404-js`,
112+
isCreatedByStatefulCreatePages: true,
113+
context: {},
114+
updatedAt: 1557740602382,
115+
pluginCreator___NODE: `f795702c-a3b8-5a88-88ee-5d06019d44fa`,
116+
pluginCreatorId: `f795702c-a3b8-5a88-88ee-5d06019d44fa`,
117+
},
118+
],
119+
]),
120+
manifest: {
121+
"main.js": `render-page.js`,
122+
"main.js.map": `render-page.js.map`,
123+
app: [
124+
`webpack-runtime-acaa8994f1f704475e21.js`,
125+
`styles.1025963f4f2ec7abbad4.css`,
126+
`styles-565f081c8374bbda155f.js`,
127+
`app-f33c13590352da20930f.js`,
128+
],
129+
"component---node-modules-gatsby-plugin-offline-app-shell-js": [
130+
`component---node-modules-gatsby-plugin-offline-app-shell-js-78f9e4dea04737fa062d.js`,
131+
],
132+
"component---src-templates-blog-post-js": [
133+
`0-0180cd94ef2497ac7db8.js`,
134+
`component---src-templates-blog-post-js-517987eae96e75cddbe7.js`,
135+
],
136+
"component---src-pages-404-js": [
137+
`0-0180cd94ef2497ac7db8.js`,
138+
`component---src-pages-404-js-53e6c51a5a7e73090f50.js`,
139+
],
140+
"component---src-pages-index-js": [
141+
`0-0180cd94ef2497ac7db8.js`,
142+
`component---src-pages-index-js-0bdd01c77ee09ef0224c.js`,
143+
],
144+
"pages-manifest": [`pages-manifest-ab11f09e0ca7ecd3b43e.js`],
145+
},
146+
pathPrefix: ``,
147+
publicFolder: fileName => path.join(tmpDir, fileName),
148+
}
149+
}
150+
151+
test(`with caching headers`, async () => {
152+
const pluginData = await createPluginData()
153+
154+
const pluginOptions = {
155+
...DEFAULT_OPTIONS,
156+
mergeCachingHeaders: true,
157+
}
158+
159+
await buildHeadersProgram(pluginData, pluginOptions)
160+
161+
expect(
162+
await fs.readFile(pluginData.publicFolder(`_headers`), `utf8`)
163+
).toMatchSnapshot()
164+
})
165+
166+
test(`without caching headers`, async () => {
167+
const pluginData = await createPluginData()
168+
169+
const pluginOptions = {
170+
...DEFAULT_OPTIONS,
171+
mergeCachingHeaders: false,
172+
}
173+
174+
await buildHeadersProgram(pluginData, pluginOptions)
175+
176+
expect(
177+
await fs.readFile(pluginData.publicFolder(`_headers`), `utf8`)
178+
).toMatchSnapshot()
179+
})

packages/gatsby-plugin-netlify/src/build-headers-program.js

+21-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import _ from "lodash"
22
import { writeFile, existsSync } from "fs-extra"
33
import { parse } from "path"
44
import kebabHash from "kebab-hash"
5-
import { HEADER_COMMENT } from "./constants"
5+
import { HEADER_COMMENT, IMMUTABLE_CACHING_HEADER } from "./constants"
66

77
import {
88
COMMON_BUNDLES,
@@ -219,12 +219,29 @@ const applySecurityHeaders = ({ mergeSecurityHeaders }) => headers => {
219219
return defaultMerge(headers, SECURITY_HEADERS)
220220
}
221221

222-
const applyCachingHeaders = ({ mergeCachingHeaders }) => headers => {
222+
const applyCachingHeaders = (
223+
pluginData,
224+
{ mergeCachingHeaders }
225+
) => headers => {
223226
if (!mergeCachingHeaders) {
224227
return headers
225228
}
226229

227-
return defaultMerge(headers, CACHING_HEADERS)
230+
const chunks = Array.from(pluginData.pages.values()).map(
231+
page => page.componentChunkName
232+
)
233+
234+
chunks.push(`pages-manifest`, `app`)
235+
236+
const files = [].concat(...chunks.map(chunk => pluginData.manifest[chunk]))
237+
238+
const cachingHeaders = {}
239+
240+
files.forEach(file => {
241+
cachingHeaders[`/` + file] = [IMMUTABLE_CACHING_HEADER]
242+
})
243+
244+
return defaultMerge(headers, cachingHeaders, CACHING_HEADERS)
228245
}
229246

230247
const applyTransfromHeaders = ({ transformHeaders }) => headers =>
@@ -241,7 +258,7 @@ export default function buildHeadersProgram(pluginData, pluginOptions) {
241258
validateUserOptions(pluginOptions),
242259
mapUserLinkHeaders(pluginData),
243260
applySecurityHeaders(pluginOptions),
244-
applyCachingHeaders(pluginOptions),
261+
applyCachingHeaders(pluginData, pluginOptions),
245262
mapUserLinkAllPageHeaders(pluginData, pluginOptions),
246263
applyLinkHeaders(pluginData, pluginOptions),
247264
applyTransfromHeaders(pluginOptions),

packages/gatsby-plugin-netlify/src/constants.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ export const SECURITY_HEADERS = {
2525
],
2626
}
2727

28+
export const IMMUTABLE_CACHING_HEADER = `Cache-Control: public, max-age=31536000, immutable`
29+
2830
export const CACHING_HEADERS = {
29-
"/static/*": [`Cache-Control: public, max-age=31536000, immutable`],
31+
"/static/*": [IMMUTABLE_CACHING_HEADER],
3032
"/sw.js": [`Cache-Control: no-cache`],
3133
}
3234

0 commit comments

Comments
 (0)