Skip to content

Commit d42023d

Browse files
author
Peter Bengtsson
authored
cache /categories.json (#23635)
1 parent b2411af commit d42023d

2 files changed

Lines changed: 51 additions & 26 deletions

File tree

middleware/categories-for-support.js

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,59 @@
11
import path from 'path'
2+
3+
import { cacheControlFactory } from './cache-control.js'
4+
25
const renderOpts = { textOnly: true, encodeEntities: true }
36

7+
const cacheControl = cacheControlFactory(60 * 60 * 24)
8+
9+
// Module-global variable that gets populate once the categoriesForSupport()
10+
// runs at least once.
11+
let globalAllCategoriesCache = null
12+
413
// This middleware exposes a list of all categories and child articles at /categories.json.
514
// GitHub Support uses this for internal ZenDesk search functionality.
615
export default async function categoriesForSupport(req, res, next) {
716
const englishSiteTree = req.context.siteTree.en
817

9-
const allCategories = []
18+
const allCategories = globalAllCategoriesCache || []
19+
if (!allCategories.length) {
20+
await Promise.all(
21+
Object.keys(englishSiteTree).map(async (version) => {
22+
await Promise.all(
23+
englishSiteTree[version].childPages.map(async (productPage) => {
24+
if (productPage.page.relativePath.startsWith('early-access')) return
25+
if (!productPage.childPages) return
1026

11-
await Promise.all(
12-
Object.keys(englishSiteTree).map(async (version) => {
13-
await Promise.all(
14-
englishSiteTree[version].childPages.map(async (productPage) => {
15-
if (productPage.page.relativePath.startsWith('early-access')) return
16-
if (!productPage.childPages) return
17-
18-
await Promise.all(
19-
productPage.childPages.map(async (categoryPage) => {
20-
// We can't get the rendered titles from middleware/render-tree-titles
21-
// here because that middleware only runs on the current version, and this
22-
// middleware processes all versions.
23-
const name = categoryPage.page.title.includes('{')
24-
? await categoryPage.page.renderProp('title', req.context, renderOpts)
25-
: categoryPage.page.title
26-
27-
allCategories.push({
28-
name,
29-
published_articles: await findArticlesPerCategory(categoryPage, [], req.context),
27+
await Promise.all(
28+
productPage.childPages.map(async (categoryPage) => {
29+
// We can't get the rendered titles from middleware/render-tree-titles
30+
// here because that middleware only runs on the current version, and this
31+
// middleware processes all versions.
32+
const name = categoryPage.page.title.includes('{')
33+
? await categoryPage.page.renderProp('title', req.context, renderOpts)
34+
: categoryPage.page.title
35+
36+
allCategories.push({
37+
name,
38+
published_articles: await findArticlesPerCategory(categoryPage, [], req.context),
39+
})
3040
})
31-
})
32-
)
33-
})
34-
)
35-
})
36-
)
41+
)
42+
})
43+
)
44+
})
45+
)
46+
if (allCategories.length && process.env.NODE_ENV !== 'development') {
47+
globalAllCategoriesCache = allCategories
48+
}
49+
}
50+
51+
// Cache somewhat aggressively but note that it will be soft-purged
52+
// in every prod deployment.
53+
cacheControl(res)
54+
55+
// Undo the cookie setting that CSRF sets.
56+
res.removeHeader('set-cookie')
3757

3858
return res.json(allCategories)
3959
}

tests/rendering/server.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ describe('server', () => {
247247
// check for CORS header
248248
expect(res.headers['access-control-allow-origin']).toBe('*')
249249

250+
// Check that it can be cached at the CDN
251+
expect(res.headers['set-cookie']).toBeUndefined()
252+
expect(res.headers['cache-control']).toContain('public')
253+
expect(res.headers['cache-control']).toMatch(/max-age=\d+/)
254+
250255
const categories = JSON.parse(res.text)
251256
expect(Array.isArray(categories)).toBe(true)
252257
expect(categories.length).toBeGreaterThan(1)

0 commit comments

Comments
 (0)