Skip to content

Commit

Permalink
Handle getStaticPaths error inside worker to avoid serializing (#39032)
Browse files Browse the repository at this point in the history
* Handle getStaticPaths error inside worker to avoid serializing

* handle invalid export case
  • Loading branch information
ijjk committed Jul 26, 2022
1 parent cee2cf3 commit b846026
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 102 deletions.
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')
})
})

0 comments on commit b846026

Please sign in to comment.