Skip to content
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

Handle getStaticPaths error inside worker to avoid serializing #39032

Merged
merged 2 commits into from Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
204 changes: 106 additions & 98 deletions packages/next/build/utils.ts
Expand Up @@ -862,117 +862,125 @@ export async function isPageStatic(
traceExcludes?: string[]
}> {
const isPageStaticSpan = trace('is-page-static-utils', parentId)
return isPageStaticSpan.traceAsyncFn(async () => {
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
setHttpAgentOptions(httpAgentOptions)
return isPageStaticSpan
.traceAsyncFn(async () => {
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
setHttpAgentOptions(httpAgentOptions)

const mod = await loadComponents(distDir, page, serverless)
const Comp = mod.Component
const mod = await loadComponents(distDir, page, serverless)
const Comp = mod.Component

if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') {
throw new Error('INVALID_DEFAULT_EXPORT')
}
if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') {
throw new Error('INVALID_DEFAULT_EXPORT')
}

const hasGetInitialProps = !!(Comp as any).getInitialProps
const hasStaticProps = !!mod.getStaticProps
const hasStaticPaths = !!mod.getStaticPaths
const hasServerProps = !!mod.getServerSideProps
const hasLegacyServerProps = !!(await mod.ComponentMod
.unstable_getServerProps)
const hasLegacyStaticProps = !!(await mod.ComponentMod
.unstable_getStaticProps)
const hasLegacyStaticPaths = !!(await mod.ComponentMod
.unstable_getStaticPaths)
const hasLegacyStaticParams = !!(await mod.ComponentMod
.unstable_getStaticParams)

if (hasLegacyStaticParams) {
throw new Error(
`unstable_getStaticParams was replaced with getStaticPaths. Please update your code.`
)
}
const hasGetInitialProps = !!(Comp as any).getInitialProps
const hasStaticProps = !!mod.getStaticProps
const hasStaticPaths = !!mod.getStaticPaths
const hasServerProps = !!mod.getServerSideProps
const hasLegacyServerProps = !!(await mod.ComponentMod
.unstable_getServerProps)
const hasLegacyStaticProps = !!(await mod.ComponentMod
.unstable_getStaticProps)
const hasLegacyStaticPaths = !!(await mod.ComponentMod
.unstable_getStaticPaths)
const hasLegacyStaticParams = !!(await mod.ComponentMod
.unstable_getStaticParams)

if (hasLegacyStaticParams) {
throw new Error(
`unstable_getStaticParams was replaced with getStaticPaths. Please update your code.`
)
}

if (hasLegacyStaticPaths) {
throw new Error(
`unstable_getStaticPaths was replaced with getStaticPaths. Please update your code.`
)
}
if (hasLegacyStaticPaths) {
throw new Error(
`unstable_getStaticPaths was replaced with getStaticPaths. Please update your code.`
)
}

if (hasLegacyStaticProps) {
throw new Error(
`unstable_getStaticProps was replaced with getStaticProps. Please update your code.`
)
}
if (hasLegacyStaticProps) {
throw new Error(
`unstable_getStaticProps was replaced with getStaticProps. Please update your code.`
)
}

if (hasLegacyServerProps) {
throw new Error(
`unstable_getServerProps was replaced with getServerSideProps. Please update your code.`
)
}
if (hasLegacyServerProps) {
throw new Error(
`unstable_getServerProps was replaced with getServerSideProps. Please update your code.`
)
}

// A page cannot be prerendered _and_ define a data requirement. That's
// contradictory!
if (hasGetInitialProps && hasStaticProps) {
throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT)
}
// A page cannot be prerendered _and_ define a data requirement. That's
// contradictory!
if (hasGetInitialProps && hasStaticProps) {
throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT)
}

if (hasGetInitialProps && hasServerProps) {
throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT)
}
if (hasGetInitialProps && hasServerProps) {
throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT)
}

if (hasStaticProps && hasServerProps) {
throw new Error(SERVER_PROPS_SSG_CONFLICT)
}
if (hasStaticProps && hasServerProps) {
throw new Error(SERVER_PROPS_SSG_CONFLICT)
}

const pageIsDynamic = isDynamicRoute(page)
// A page cannot have static parameters if it is not a dynamic page.
if (hasStaticProps && hasStaticPaths && !pageIsDynamic) {
throw new Error(
`getStaticPaths can only be used with dynamic pages, not '${page}'.` +
`\nLearn more: https://nextjs.org/docs/routing/dynamic-routes`
)
}
const pageIsDynamic = isDynamicRoute(page)
// A page cannot have static parameters if it is not a dynamic page.
if (hasStaticProps && hasStaticPaths && !pageIsDynamic) {
throw new Error(
`getStaticPaths can only be used with dynamic pages, not '${page}'.` +
`\nLearn more: https://nextjs.org/docs/routing/dynamic-routes`
)
}

if (hasStaticProps && pageIsDynamic && !hasStaticPaths) {
throw new Error(
`getStaticPaths is required for dynamic SSG pages and is missing for '${page}'.` +
`\nRead more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value`
)
}
if (hasStaticProps && pageIsDynamic && !hasStaticPaths) {
throw new Error(
`getStaticPaths is required for dynamic SSG pages and is missing for '${page}'.` +
`\nRead more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value`
)
}

let prerenderRoutes: Array<string> | undefined
let encodedPrerenderRoutes: Array<string> | undefined
let prerenderFallback: boolean | 'blocking' | undefined
if (hasStaticProps && hasStaticPaths) {
;({
paths: prerenderRoutes,
fallback: prerenderFallback,
encodedPaths: encodedPrerenderRoutes,
} = await buildStaticPaths(
page,
mod.getStaticPaths!,
configFileName,
locales,
defaultLocale
))
}
let prerenderRoutes: Array<string> | undefined
let encodedPrerenderRoutes: Array<string> | undefined
let prerenderFallback: boolean | 'blocking' | undefined
if (hasStaticProps && hasStaticPaths) {
;({
paths: prerenderRoutes,
fallback: prerenderFallback,
encodedPaths: encodedPrerenderRoutes,
} = await buildStaticPaths(
page,
mod.getStaticPaths!,
configFileName,
locales,
defaultLocale
))
}

const isNextImageImported = (global as any).__NEXT_IMAGE_IMPORTED
const config: PageConfig = mod.pageConfig
return {
isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps,
isHybridAmp: config.amp === 'hybrid',
isAmpOnly: config.amp === true,
prerenderRoutes,
prerenderFallback,
encodedPrerenderRoutes,
hasStaticProps,
hasServerProps,
isNextImageImported,
traceIncludes: config.unstable_includeFiles || [],
traceExcludes: config.unstable_excludeFiles || [],
}
})
const isNextImageImported = (global as any).__NEXT_IMAGE_IMPORTED
const config: PageConfig = mod.pageConfig
return {
isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps,
isHybridAmp: config.amp === 'hybrid',
isAmpOnly: config.amp === true,
prerenderRoutes,
prerenderFallback,
encodedPrerenderRoutes,
hasStaticProps,
hasServerProps,
isNextImageImported,
traceIncludes: config.unstable_includeFiles || [],
traceExcludes: config.unstable_excludeFiles || [],
}
})
.catch((err) => {
if (err.message === 'INVALID_DEFAULT_EXPORT') {
throw err
}
console.error(err)
throw new Error(`Failed to collect page data for ${page}`)
})
}

export async function hasCustomGetInitialProps(
Expand Down
67 changes: 63 additions & 4 deletions test/integration/gsp-build-errors/test/index.test.js
@@ -1,15 +1,14 @@
/* eslint-env jest */

import fs from 'fs-extra'
import { join } from 'path'
import { dirname, join } from 'path'
import { nextBuild } from 'next-test-utils'

const appDir = join(__dirname, '..')
const pagesDir = join(appDir, 'pages')
const testPage = join(pagesDir, 'test.js')

const writePage = async (content) => {
await fs.ensureDir(pagesDir)
const writePage = async (content, testPage = join(pagesDir, 'test.js')) => {
await fs.ensureDir(dirname(testPage))
await fs.writeFile(testPage, content)
}

Expand Down Expand Up @@ -107,4 +106,64 @@ describe('GSP build errors', () => {
expect(code).toBe(1)
expect(stderr).toContain('a string error')
})

it('should handle non-serializable error in getStaticProps', async () => {
await writePage(`
export function getStaticProps() {
const err = new Error('my custom error')
err.hello = 'world'
err.a = [1,2,3]
err.original = err
err.b = err.a

throw err

return {
props: {}
}
}

export default function () {
return null
}
`)
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(1)
expect(stderr).toContain('my custom error')
})

it('should handle non-serializable error in getStaticPaths', async () => {
await writePage(
`
export function getStaticProps() {
return {
props: {}
}
}

export function getStaticPaths() {
const err = new Error('my custom error')
err.hello = 'world'
err.a = [1,2,3]
err.original = err
err.b = err.a

throw err

return {
paths: [],
fallback: true
}
}

export default function () {
return null
}
`,
join(pagesDir, '[slug].js')
)
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(1)
expect(stderr).toContain('my custom error')
})
})