-
Notifications
You must be signed in to change notification settings - Fork 86
fix: don't use ODB for routes that match middleware #1171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
c6ff51f
2711061
cb061ca
53a0bdd
ff9445a
012612a
2e9ddb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { useRouter } from 'next/router' | ||
import Link from 'next/link' | ||
|
||
const Show = ({ show }) => { | ||
const router = useRouter() | ||
|
||
// This is never shown on Netlify. We just need it for NextJS to be happy, | ||
// because NextJS will render a fallback HTML page. | ||
if (router.isFallback) { | ||
return <div>Loading...</div> | ||
} | ||
|
||
return ( | ||
<div> | ||
<p> | ||
Check the network panel for the header <code>x-middleware-date</code> to ensure that it is running | ||
</p> | ||
|
||
<hr /> | ||
|
||
<h1>Show #{show.id}</h1> | ||
<p>{show.name}</p> | ||
|
||
<hr /> | ||
|
||
<Link href="/"> | ||
<a>Go back home</a> | ||
</Link> | ||
</div> | ||
) | ||
} | ||
|
||
export async function getStaticPaths() { | ||
// Set the paths we want to pre-render | ||
const paths = [{ params: { slug: ['my', 'path', '1'] } }, { params: { slug: ['my', 'path', '2'] } }] | ||
|
||
// We'll pre-render these paths at build time. | ||
// { fallback: true } means other routes will be rendered at runtime. | ||
return { paths, fallback: true } | ||
} | ||
|
||
export async function getStaticProps({ params }) { | ||
// The ID to render | ||
const { slug } = params | ||
const id = slug[slug.length - 1] | ||
|
||
const res = await fetch(`https://api.tvmaze.com/shows/${id}`) | ||
const data = await res.json() | ||
|
||
return { | ||
props: { | ||
show: data, | ||
}, | ||
} | ||
} | ||
|
||
export default Show |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { useRouter } from 'next/router' | ||
import Link from 'next/link' | ||
|
||
const Show = ({ show }) => { | ||
const router = useRouter() | ||
|
||
// This is never shown on Netlify. We just need it for NextJS to be happy, | ||
// because NextJS will render a fallback HTML page. | ||
if (router.isFallback) { | ||
return <div>Loading...</div> | ||
} | ||
|
||
return ( | ||
<div> | ||
<p> | ||
Check the network panel for the header <code>x-middleware-date</code> to ensure that it is running | ||
</p> | ||
<hr /> | ||
|
||
<h1>Show #{show.id}</h1> | ||
<p>{show.name}</p> | ||
|
||
<hr /> | ||
|
||
<Link href="/"> | ||
<a>Go back home</a> | ||
</Link> | ||
</div> | ||
) | ||
} | ||
|
||
export async function getStaticPaths() { | ||
// Set the paths we want to pre-render | ||
const paths = [{ params: { id: '3' } }, { params: { id: '4' } }] | ||
|
||
// We'll pre-render these paths at build time. | ||
// { fallback: true } means other routes will be rendered at runtime. | ||
return { paths, fallback: true } | ||
} | ||
|
||
export async function getStaticProps({ params }) { | ||
// The ID to render | ||
const { id } = params | ||
|
||
const res = await fetch(`https://api.tvmaze.com/shows/${id}`) | ||
const data = await res.json() | ||
|
||
return { | ||
props: { | ||
show: data, | ||
}, | ||
} | ||
} | ||
|
||
export default Show |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { NextResponse } from 'next/server' | ||
|
||
export function middleware() { | ||
const res = NextResponse.next() | ||
res.headers.set('x-middleware-date', new Date().toISOString()) | ||
return res | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,11 +1,15 @@ | ||||||
/* eslint-disable max-lines */ | ||||||
import { NetlifyConfig } from '@netlify/build' | ||||||
import { yellowBright } from 'chalk' | ||||||
import { readJSON } from 'fs-extra' | ||||||
import { NextConfig } from 'next' | ||||||
import { PrerenderManifest } from 'next/dist/build' | ||||||
import { outdent } from 'outdent' | ||||||
import { join } from 'pathe' | ||||||
|
||||||
import { HANDLER_FUNCTION_PATH, HIDDEN_PATHS, ODB_FUNCTION_PATH } from '../constants' | ||||||
|
||||||
import { getMiddleware } from './files' | ||||||
import { RoutesManifest } from './types' | ||||||
import { | ||||||
getApiRewrites, | ||||||
|
@@ -14,9 +18,11 @@ import { | |||||
redirectsForNextRoute, | ||||||
redirectsForNextRouteWithData, | ||||||
routeToDataRoute, | ||||||
targetForFallback, | ||||||
} from './utils' | ||||||
|
||||||
const matchesMiddleware = (middleware: Array<string>, route: string): boolean => | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
[sand] Or optional? I see you check the existence in the line below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like |
||||||
middleware?.some((middlewarePath) => route.startsWith(middlewarePath)) | ||||||
|
||||||
const generateLocaleRedirects = ({ | ||||||
i18n, | ||||||
basePath, | ||||||
|
@@ -65,6 +71,7 @@ export const generateStaticRedirects = ({ | |||||
} | ||||||
} | ||||||
|
||||||
// eslint-disable-next-line max-lines-per-function | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [sand] This function is indeed starting to get pretty complex. Could you split it out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'\ve split out the various bits |
||||||
export const generateRedirects = async ({ | ||||||
netlifyConfig, | ||||||
nextConfig: { i18n, basePath, trailingSlash, appDir }, | ||||||
|
@@ -102,6 +109,36 @@ export const generateRedirects = async ({ | |||||
...(await getPreviewRewrites({ basePath, appDir })), | ||||||
) | ||||||
|
||||||
const middleware = await getMiddleware(netlifyConfig.build.publish) | ||||||
const routesThatMatchMiddleware = new Set<string>() | ||||||
|
||||||
const handlerRewrite = (from: string) => ({ | ||||||
from: `${basePath}${from}`, | ||||||
to: HANDLER_FUNCTION_PATH, | ||||||
status: 200, | ||||||
}) | ||||||
|
||||||
// Routes that match middleware need to always use the SSR function | ||||||
// This generates a rewrite for every middleware in every locale, both with and without a splat | ||||||
netlifyConfig.redirects.push( | ||||||
...middleware | ||||||
.map((route) => { | ||||||
const unlocalized = [handlerRewrite(`${route}`), handlerRewrite(`${route}/*`)] | ||||||
if (i18n?.locales?.length > 0) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Explicitly testing against zero is in the linting rules |
||||||
const localized = i18n?.locales?.map((locale) => [ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Since the above check prevents it, you don't need to have the optional check here |
||||||
handlerRewrite(`/${locale}${route}`), | ||||||
handlerRewrite(`/${locale}${route}/*`), | ||||||
handlerRewrite(`/_next/data/${buildId}/${locale}${route}/*`), | ||||||
]) | ||||||
// With i18n, all data routes are prefixed with the locale, but the HTML also has the unprefixed default | ||||||
return [...unlocalized, ...localized] | ||||||
} | ||||||
return [...unlocalized, handlerRewrite(`/_next/data/${buildId}${route}/*`)] | ||||||
}) | ||||||
// Flatten the array of arrays. Can't use flatMap as it might be 2 levels deep | ||||||
.flat(2), | ||||||
) | ||||||
|
||||||
const staticRouteEntries = Object.entries(prerenderedStaticRoutes) | ||||||
|
||||||
const staticRoutePaths = new Set<string>() | ||||||
|
@@ -121,7 +158,9 @@ export const generateRedirects = async ({ | |||||
if (i18n?.defaultLocale && route.startsWith(`/${i18n.defaultLocale}/`)) { | ||||||
route = route.slice(i18n.defaultLocale.length + 1) | ||||||
staticRoutePaths.add(route) | ||||||
|
||||||
if (matchesMiddleware(middleware, route)) { | ||||||
routesThatMatchMiddleware.add(route) | ||||||
} | ||||||
netlifyConfig.redirects.push( | ||||||
...redirectsForNextRouteWithData({ | ||||||
route, | ||||||
|
@@ -131,6 +170,9 @@ export const generateRedirects = async ({ | |||||
force: true, | ||||||
}), | ||||||
) | ||||||
} else if (matchesMiddleware(middleware, route)) { | ||||||
// Routes that match middleware can't use the ODB | ||||||
routesThatMatchMiddleware.add(route) | ||||||
} else { | ||||||
// ISR routes use the ODB handler | ||||||
netlifyConfig.redirects.push( | ||||||
|
@@ -155,11 +197,13 @@ export const generateRedirects = async ({ | |||||
return | ||||||
} | ||||||
if (route.page in prerenderedDynamicRoutes) { | ||||||
const { fallback } = prerenderedDynamicRoutes[route.page] | ||||||
|
||||||
const { to, status } = targetForFallback(fallback) | ||||||
|
||||||
netlifyConfig.redirects.push(...redirectsForNextRoute({ buildId, route: route.page, basePath, to, status, i18n })) | ||||||
if (matchesMiddleware(middleware, route.page)) { | ||||||
routesThatMatchMiddleware.add(route.page) | ||||||
} else { | ||||||
netlifyConfig.redirects.push( | ||||||
...redirectsForNextRoute({ buildId, route: route.page, basePath, to: ODB_FUNCTION_PATH, status: 200, i18n }), | ||||||
) | ||||||
} | ||||||
} else { | ||||||
// If the route isn't prerendered, it's SSR | ||||||
netlifyConfig.redirects.push( | ||||||
|
@@ -174,4 +218,19 @@ export const generateRedirects = async ({ | |||||
to: HANDLER_FUNCTION_PATH, | ||||||
status: 200, | ||||||
}) | ||||||
|
||||||
const middlewareMatches = routesThatMatchMiddleware.size | ||||||
if (middlewareMatches > 0) { | ||||||
console.log( | ||||||
yellowBright(outdent` | ||||||
There ${ | ||||||
middlewareMatches === 1 | ||||||
? `is one statically-generated or ISR route` | ||||||
: `are ${middlewareMatches} statically-generated or ISR routes` | ||||||
} that match a middleware function, which means they will always be served from the SSR function and will not use ISR or be served from the CDN. | ||||||
If this was not intended, ensure that your middleware only matches routes that you intend to use SSR. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @KyleBlankRollins for this message 😊
ascorbic marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
`), | ||||||
) | ||||||
} | ||||||
} | ||||||
/* eslint-enable max-lines */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No error handling? It looks like
readJSON
also allows you to pass it{ throws: false }
so it doesn't throw if it fails to read the JSON.