@@ -36,6 +36,7 @@ import loadCustomRoutes, {
3636 normalizeRouteRegex ,
3737 Redirect ,
3838 Rewrite ,
39+ RouteHas ,
3940 RouteType ,
4041} from '../lib/load-custom-routes'
4142import { getRedirectStatus , modifyRouteRegex } from '../lib/redirect-status'
@@ -130,6 +131,7 @@ import { flatReaddir } from '../lib/flat-readdir'
130131import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
131132import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
132133import {
134+ ACTION ,
133135 NEXT_ROUTER_PREFETCH ,
134136 RSC ,
135137 RSC_CONTENT_TYPE_HEADER ,
@@ -160,13 +162,15 @@ export type SsgRoute = {
160162 dataRoute : string | null
161163 initialStatus ?: number
162164 initialHeaders ?: Record < string , string >
165+ experimentalBypassFor ?: RouteHas [ ]
163166}
164167
165168export type DynamicSsgRoute = {
166169 routeRegex : string
167170 fallback : string | null | false
168171 dataRoute : string | null
169172 dataRouteRegex : string | null
173+ experimentalBypassFor ?: RouteHas [ ]
170174}
171175
172176export type PrerenderManifest = {
@@ -1612,14 +1616,6 @@ export default async function build(
16121616 `Using edge runtime on a page currently disables static generation for that page`
16131617 )
16141618 } else {
1615- // If a page has action and it is static, we need to
1616- // change it to SSG to keep the worker created.
1617- // TODO: This is a workaround for now, we should have a
1618- // dedicated worker defined in a heuristic way.
1619- const hasAction = entriesWithAction ?. has (
1620- 'app' + originalAppPath
1621- )
1622-
16231619 if (
16241620 workerResult . encodedPrerenderRoutes &&
16251621 workerResult . prerenderRoutes
@@ -1638,47 +1634,39 @@ export default async function build(
16381634
16391635 const appConfig = workerResult . appConfig || { }
16401636 if ( appConfig . revalidate !== 0 ) {
1641- if ( hasAction ) {
1642- Log . warnOnce (
1643- `Using server actions on a page currently disables static generation for that page`
1637+ const isDynamic = isDynamicRoute ( page )
1638+ const hasGenerateStaticParams =
1639+ ! ! workerResult . prerenderRoutes ?. length
1640+
1641+ if (
1642+ config . output === 'export' &&
1643+ isDynamic &&
1644+ ! hasGenerateStaticParams
1645+ ) {
1646+ throw new Error (
1647+ `Page "${ page } " is missing "generateStaticParams()" so it cannot be used with "output: export" config.`
16441648 )
1645- } else {
1646- const isDynamic = isDynamicRoute ( page )
1647- const hasGenerateStaticParams =
1648- ! ! workerResult . prerenderRoutes ?. length
1649-
1650- if (
1651- config . output === 'export' &&
1652- isDynamic &&
1653- ! hasGenerateStaticParams
1654- ) {
1655- throw new Error (
1656- `Page "${ page } " is missing "generateStaticParams()" so it cannot be used with "output: export" config.`
1657- )
1658- }
1659-
1660- if (
1661- // Mark the app as static if:
1662- // - It has no dynamic param
1663- // - It doesn't have generateStaticParams but `dynamic` is set to
1664- // `error` or `force-static`
1665- ! isDynamic
1666- ) {
1667- appStaticPaths . set ( originalAppPath , [ page ] )
1668- appStaticPathsEncoded . set ( originalAppPath , [
1669- page ,
1670- ] )
1671- isStatic = true
1672- } else if (
1673- isDynamic &&
1674- ! hasGenerateStaticParams &&
1675- ( appConfig . dynamic === 'error' ||
1676- appConfig . dynamic === 'force-static' )
1677- ) {
1678- appStaticPaths . set ( originalAppPath , [ ] )
1679- appStaticPathsEncoded . set ( originalAppPath , [ ] )
1680- isStatic = true
1681- }
1649+ }
1650+
1651+ if (
1652+ // Mark the app as static if:
1653+ // - It has no dynamic param
1654+ // - It doesn't have generateStaticParams but `dynamic` is set to
1655+ // `error` or `force-static`
1656+ ! isDynamic
1657+ ) {
1658+ appStaticPaths . set ( originalAppPath , [ page ] )
1659+ appStaticPathsEncoded . set ( originalAppPath , [ page ] )
1660+ isStatic = true
1661+ } else if (
1662+ isDynamic &&
1663+ ! hasGenerateStaticParams &&
1664+ ( appConfig . dynamic === 'error' ||
1665+ appConfig . dynamic === 'force-static' )
1666+ ) {
1667+ appStaticPaths . set ( originalAppPath , [ ] )
1668+ appStaticPathsEncoded . set ( originalAppPath , [ ] )
1669+ isStatic = true
16821670 }
16831671 }
16841672
@@ -2681,6 +2669,17 @@ export default async function build(
26812669
26822670 const isRouteHandler = isAppRouteRoute ( originalAppPath )
26832671
2672+ // this flag is used to selectively bypass the static cache and invoke the lambda directly
2673+ // to enable server actions on static routes
2674+ const bypassFor : RouteHas [ ] = [
2675+ { type : 'header' , key : ACTION } ,
2676+ {
2677+ type : 'header' ,
2678+ key : 'content-type' ,
2679+ value : 'multipart/form-data' ,
2680+ } ,
2681+ ]
2682+
26842683 routes . forEach ( ( route ) => {
26852684 if ( isDynamicRoute ( page ) && route === page ) return
26862685 if ( route === '/_not-found' ) return
@@ -2708,10 +2707,7 @@ export default async function build(
27082707 ? null
27092708 : path . posix . join ( `${ normalizedRoute } .rsc` )
27102709
2711- const routeMeta : {
2712- initialStatus ?: SsgRoute [ 'initialStatus' ]
2713- initialHeaders ?: SsgRoute [ 'initialHeaders' ]
2714- } = { }
2710+ const routeMeta : Partial < SsgRoute > = { }
27152711
27162712 const exportRouteMeta : {
27172713 status ?: number
@@ -2748,6 +2744,7 @@ export default async function build(
27482744
27492745 finalPrerenderRoutes [ route ] = {
27502746 ...routeMeta ,
2747+ experimentalBypassFor : bypassFor ,
27512748 initialRevalidateSeconds : revalidate ,
27522749 srcRoute : page ,
27532750 dataRoute,
@@ -2771,6 +2768,7 @@ export default async function build(
27712768 // TODO: create a separate manifest to allow enforcing
27722769 // dynamicParams for non-static paths?
27732770 finalDynamicRoutes [ page ] = {
2771+ experimentalBypassFor : bypassFor ,
27742772 routeRegex : normalizeRouteRegex (
27752773 getNamedRouteRegex ( page , false ) . re . source
27762774 ) ,
0 commit comments