Skip to content

Commit

Permalink
feat: add nextjs middleware handling (github#19139)
Browse files Browse the repository at this point in the history
* feat: add nextjs middleware handling split

* fix: eslint errors

* fix: filter boolean from csp list

* fix: feature flag nextjs server start

* feat: add prettier rules for ts,tsx files

* fix: remove unnecessary async from next middleware

* fix: next middleware name

* Update tsconfig.json

Co-authored-by: James M. Greene <JamesMGreene@github.com>

* Update next-env.d.ts

Co-authored-by: James M. Greene <JamesMGreene@github.com>

* fix: add typescript linting to lint command

* add comment for unsafe-eval, update webpack to use eval in development

* fix: feature flag typo

Co-authored-by: James M. Greene <JamesMGreene@github.com>
  • Loading branch information
mikesurowiec and JamesMGreene authored May 5, 2021
1 parent 7dc54c5 commit eaddbc5
Show file tree
Hide file tree
Showing 16 changed files with 1,658 additions and 38 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ coverage/
/content/early-access
/data/early-access
dist
.next

# blc: broken link checker
blc_output.log
Expand Down
14 changes: 11 additions & 3 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
{
"overrides": [
{
"files":[
"**/*.{yml,yaml}"
],
"files": ["**/*.{yml,yaml}"],
"options": {
"singleQuote": true
}
},
{
"files": ["**/*.{ts,tsx}"],
"options": {
"semi": false,
"singleQuote": true,
"printWidth": 100,
"jsxBracketSameLine": false,
"arrowParens": "always"
}
}
]
}
3 changes: 3 additions & 0 deletions components/ExampleComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ExampleComponent = () => {
return <div>Welcome to Next.JS land!</div>
}
3 changes: 2 additions & 1 deletion feature-flags.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"FEATURE_TEST_TRUE": true,
"FEATURE_TEST_FALSE": false,
"FEATURE_NEW_SITETREE": false
"FEATURE_NEW_SITETREE": false,
"FEATURE_NEXTJS": false
}
7 changes: 5 additions & 2 deletions middleware/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ module.exports = function csp (req, res, next) {
],
scriptSrc: [
"'self'",
'data:'
],
'data:',
// For use during development only! This allows us to use a performant webpack devtool setting (eval)
// https://webpack.js.org/configuration/devtool/#devtool
process.env.NODE_ENV === 'development' && "'unsafe-eval'"
].filter(Boolean),
frameSrc: [ // exceptions for GraphQL Explorer
'https://graphql-explorer.githubapp.com', // production env
'https://graphql.github.com/',
Expand Down
5 changes: 5 additions & 0 deletions middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ module.exports = function (app) {
// *** Headers for pages only ***
app.use(require('./set-fastly-cache-headers'))

// handle serving NextJS bundled code (/_next/*)
if (process.env.FEATURE_NEXTJS) {
app.use(instrument('./next'))
}

// Check for a dropped connection before proceeding (again)
app.use(haltOnDroppedConnection)

Expand Down
21 changes: 21 additions & 0 deletions middleware/next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const next = require('next')

const { NODE_ENV, FEATURE_NEXTJS } = process.env
const isDevelopment = NODE_ENV === 'development'

let nextHandleRequest
if (FEATURE_NEXTJS) {
const nextApp = next({ dev: isDevelopment })
nextHandleRequest = nextApp.getRequestHandler()
nextApp.prepare()
}

module.exports = function renderPageWithNext (req, res, next) {
if (req.path.startsWith('/_next/')) {
return nextHandleRequest(req, res)
}

next()
}

module.exports.nextHandleRequest = nextHandleRequest
34 changes: 24 additions & 10 deletions middleware/render-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ const Page = require('../lib/page')
const statsd = require('../lib/statsd')
const RedisAccessor = require('../lib/redis-accessor')
const { isConnectionDropped } = require('./halt-on-dropped-connection')
const { nextHandleRequest } = require('./next')

const { HEROKU_RELEASE_VERSION } = process.env
const { HEROKU_RELEASE_VERSION, FEATURE_NEXTJS } = process.env
const pageCacheDatabaseNumber = 1
const pageCacheExpiration = 24 * 60 * 60 * 1000 // 24 hours

Expand All @@ -25,6 +26,12 @@ const pageCache = new RedisAccessor({
// a list of query params that *do* alter the rendered page, and therefore should be cached separately
const cacheableQueries = ['learn']

const renderWithNext = FEATURE_NEXTJS
? [
'/en/rest'
]
: []

function addCsrf (req, text) {
return text.replace('$CSRFTOKEN$', req.csrfToken())
}
Expand Down Expand Up @@ -65,7 +72,10 @@ module.exports = async function renderPage (req, res, next) {
// Is the request for JSON debugging info?
const isRequestingJsonForDebugging = 'json' in req.query && process.env.NODE_ENV !== 'production'

if (isCacheable && !isRequestingJsonForDebugging) {
// Should the current path be rendered by NextJS?
const isNextJsRequest = renderWithNext.includes(req.path)

if (isCacheable && !isRequestingJsonForDebugging && !(FEATURE_NEXTJS && isNextJsRequest)) {
// Stop processing if the connection was already dropped
if (isConnectionDropped(req, res)) return

Expand Down Expand Up @@ -136,15 +146,19 @@ module.exports = async function renderPage (req, res, next) {
}
}

// currentLayout is added to the context object in middleware/contextualizers/layouts
const output = await liquid.parseAndRender(req.context.currentLayout, context)
if (FEATURE_NEXTJS && isNextJsRequest) {
nextHandleRequest(req, res)
} else {
// currentLayout is added to the context object in middleware/contextualizers/layouts
const output = await liquid.parseAndRender(req.context.currentLayout, context)

// First, send the response so the user isn't waiting
// NOTE: Do NOT `return` here as we still need to cache the response afterward!
res.send(addCsrf(req, output))
// First, send the response so the user isn't waiting
// NOTE: Do NOT `return` here as we still need to cache the response afterward!
res.send(addCsrf(req, output))

// Finally, save output to cache for the next time around
if (isCacheable) {
await pageCache.set(originalUrl, output, { expireIn: pageCacheExpiration })
// Finally, save output to cache for the next time around
if (isCacheable) {
await pageCache.set(originalUrl, output, { expireIn: pageCacheExpiration })
}
}
}
2 changes: 2 additions & 0 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
17 changes: 17 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { productIds } = require('./lib/all-products')

module.exports = {
i18n: {
locales: ['en', 'ja'],
defaultLocale: 'en'
},
async rewrites () {
const defaultVersionId = 'free-pro-team@latest'
return productIds.map((productId) => {
return {
source: `/${productId}/:path*`,
destination: `/${defaultVersionId}/${productId}/:path*`
}
})
}
}
Loading

0 comments on commit eaddbc5

Please sign in to comment.