Skip to content

Commit fedae25

Browse files
committed
fix: modify ssg default rendering mode
1 parent c8f93d5 commit fedae25

File tree

8 files changed

+103
-41
lines changed

8 files changed

+103
-41
lines changed

packages/cli/plugin-ssg/src/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export const createServer = async (
120120
method: 'GET',
121121
headers: {
122122
host: 'localhost',
123+
'x-modern-ssg-render': 'true',
123124
},
124125
});
125126

packages/runtime/plugin-runtime/src/cli/code.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import path from 'path';
22
import type {
3-
AppNormalizedConfig,
43
AppToolsContext,
54
AppToolsFeatureHooks,
65
AppToolsNormalizedConfig,
@@ -17,32 +16,15 @@ import {
1716
INDEX_FILE_NAME,
1817
SERVER_ENTRY_POINT_FILE_NAME,
1918
} from './constants';
19+
import { resolveSSRMode } from './ssr/mode';
2020
import * as template from './template';
2121
import * as serverTemplate from './template.server';
2222

2323
function getSSRMode(
2424
entry: string,
2525
config: AppToolsNormalizedConfig,
2626
): 'string' | 'stream' | false {
27-
const { ssr, ssrByEntries } = config.server;
28-
29-
if (config.output.ssg || config.output.ssgByEntries) {
30-
return 'string';
31-
}
32-
33-
return checkSSRMode(ssrByEntries?.[entry] || ssr);
34-
35-
function checkSSRMode(ssr: AppNormalizedConfig['server']['ssr']) {
36-
if (!ssr) {
37-
return false;
38-
}
39-
40-
if (typeof ssr === 'boolean') {
41-
return ssr ? 'string' : false;
42-
}
43-
44-
return ssr.mode === 'stream' ? 'stream' : 'string';
45-
}
27+
return resolveSSRMode({ entry, config });
4628
}
4729

4830
export const generateCode = async (

packages/runtime/plugin-runtime/src/cli/ssr/index.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
import type { CLIPluginAPI } from '@modern-js/plugin';
99
import { LOADABLE_STATS_FILE, isUseSSRBundle } from '@modern-js/utils';
1010
import type { RsbuildPlugin } from '@rsbuild/core';
11+
import { resolveSSRMode } from './mode';
1112

1213
const hasStringSSREntry = (userConfig: AppToolsNormalizedConfig): boolean => {
1314
const isStreaming = (ssr: ServerUserConfig['ssr']) =>
@@ -42,16 +43,12 @@ const hasStringSSREntry = (userConfig: AppToolsNormalizedConfig): boolean => {
4243
return false;
4344
};
4445

45-
const checkUseStringSSR = (config: AppToolsNormalizedConfig): boolean => {
46-
const { output } = config;
47-
48-
if (output?.ssg) {
49-
return true;
50-
}
51-
if (output?.ssgByEntries && Object.keys(output.ssgByEntries).length > 0) {
52-
return true;
53-
}
54-
return hasStringSSREntry(config);
46+
const checkUseStringSSR = (
47+
config: AppToolsNormalizedConfig,
48+
appDirectory?: string,
49+
): boolean => {
50+
const ssrMode = resolveSSRMode({ config, appDirectory });
51+
return ssrMode === 'string';
5552
};
5653

5754
const ssrBuilderPlugin = (
@@ -72,10 +69,13 @@ const ssrBuilderPlugin = (
7269
? 'edge'
7370
: 'node';
7471

72+
const appContext = modernAPI.getAppContext();
73+
const { appDirectory } = appContext;
74+
7575
const useLoadablePlugin =
7676
isUseSSRBundle(userConfig) &&
7777
!isServerEnvironment &&
78-
checkUseStringSSR(userConfig);
78+
checkUseStringSSR(userConfig, appDirectory);
7979

8080
return mergeEnvironmentConfig(config, {
8181
source: {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type {
2+
AppNormalizedConfig,
3+
AppToolsNormalizedConfig,
4+
} from '@modern-js/app-tools';
5+
import { isReact18, isUseRsc } from '@modern-js/utils';
6+
7+
export type SSRMode = 'string' | 'stream' | false;
8+
9+
/**
10+
* Unified SSR mode resolution function.
11+
* Priority:
12+
* 1. User's explicit server.ssr/server.ssrByEntries config
13+
* 2. If RSC is enabled, use 'stream'
14+
* 3. If SSG is enabled, use 'stream' for React 18+, 'string' otherwise
15+
* 4. Otherwise return false (no SSR)
16+
*/
17+
export function resolveSSRMode(params: {
18+
entry?: string;
19+
config: AppToolsNormalizedConfig;
20+
appDirectory?: string;
21+
}): SSRMode {
22+
const { entry, config, appDirectory } = params;
23+
24+
// 1. Check user's explicit SSR config (server.ssr or server.ssrByEntries)
25+
const ssr = entry
26+
? config.server?.ssrByEntries?.[entry] || config.server?.ssr
27+
: config.server?.ssr;
28+
29+
if (ssr !== undefined) {
30+
if (!ssr) {
31+
return false;
32+
}
33+
if (typeof ssr === 'boolean') {
34+
return ssr ? 'string' : false;
35+
}
36+
return ssr.mode === 'stream' ? 'stream' : 'string';
37+
}
38+
39+
// 2. If RSC is enabled, use streaming SSR
40+
if (isUseRsc(config)) {
41+
return 'stream';
42+
}
43+
44+
// 3. If SSG is enabled, check React version
45+
const isSsgEnabled =
46+
config.output?.ssg ||
47+
(config.output?.ssgByEntries &&
48+
(entry
49+
? !!config.output.ssgByEntries[entry]
50+
: Object.keys(config.output.ssgByEntries).length > 0));
51+
52+
if (isSsgEnabled) {
53+
// React 18+ supports streaming SSR
54+
if (appDirectory) {
55+
return isReact18(appDirectory) ? 'stream' : 'string';
56+
}
57+
// Default to React 18+ when appDirectory is not available
58+
return 'stream';
59+
}
60+
61+
// 4. No SSR
62+
return false;
63+
}

packages/runtime/plugin-runtime/src/core/server/requestHandler.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
parseHeaders,
1111
parseQuery,
1212
} from '@modern-js/runtime-utils/universal/request';
13-
import type React from 'react';
13+
import React from 'react';
1414
import { Fragment } from 'react';
1515
import {
1616
type RuntimeContext,
@@ -143,7 +143,13 @@ function createSSRContext(
143143
config.ssr,
144144
config.ssrByEntries,
145145
);
146-
const ssrMode = getSSRMode(ssrConfig);
146+
let ssrMode = getSSRMode(ssrConfig);
147+
148+
const isSsgRender = headers.get('x-modern-ssg-render') === 'true';
149+
if (isSsgRender) {
150+
const reactMajor = Number((React.version || '0').split('.')[0]);
151+
ssrMode = reactMajor >= 18 ? 'stream' : 'string';
152+
}
147153

148154
const loaderFailureMode =
149155
typeof ssrConfig === 'object' ? ssrConfig.loaderFailureMode : undefined;

packages/runtime/plugin-runtime/src/core/server/stream/createReadableStream.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ export const createReadableStreamFromElement: CreateReadableStreamFromElement =
3636
// When a crawler visit the page, we should waiting for entrie content of page
3737

3838
const isbot = checkIsBot(request.headers.get('user-agent'));
39-
const onReady = isbot || forceStream2String ? 'onAllReady' : 'onShellReady';
39+
const isSsgRender = request.headers.get('x-modern-ssg-render') === 'true';
40+
const onReady =
41+
isbot || isSsgRender || forceStream2String
42+
? 'onAllReady'
43+
: 'onShellReady';
4044

4145
const internalRuntimeContext = getGlobalInternalRuntimeContext();
4246
const hooks = internalRuntimeContext.hooks;

packages/runtime/plugin-runtime/src/core/server/stream/createReadableStream.worker.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ export const createReadableStreamFromElement: CreateReadableStreamFromElement =
5656
});
5757

5858
const isbot = checkIsBot(request.headers.get('user-agent'));
59-
if (isbot) {
60-
// However, when a crawler visits your page, or if you’re generating the pages at the build time,
59+
const isSsgRender = request.headers.get('x-modern-ssg-render') === 'true';
60+
if (isbot || isSsgRender) {
61+
// However, when a crawler visits your page, or if you're generating the pages at the build time,
6162
// you might want to let all of the content load first and then produce the final HTML output instead of revealing it progressively.
6263
// from: https://react.dev/reference/react-dom/server/renderToReadableStream#handling-different-errors-in-different-ways
6364
await readableOriginal.allReady;

packages/runtime/plugin-runtime/src/router/cli/code/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import type {
1616
import {
1717
fs,
1818
getEntryOptions,
19-
isSSGEntry,
2019
isUseRsc,
2120
isUseSSRBundle,
2221
logger,
@@ -28,6 +27,7 @@ import {
2827
} from '@modern-js/utils';
2928
import { cloneDeep } from '@modern-js/utils/lodash';
3029
import { ENTRY_POINT_RUNTIME_GLOBAL_CONTEXT_FILE_NAME } from '../../../cli/constants';
30+
import { resolveSSRMode } from '../../../cli/ssr/mode';
3131
import { FILE_SYSTEM_ROUTES_FILE_NAME } from '../constants';
3232
import { walk } from './nestedRoutes';
3333
import * as templates from './templates';
@@ -173,14 +173,19 @@ export const generateCode = async (
173173
config.server.ssrByEntries,
174174
packageName,
175175
);
176-
const useSSG = isSSGEntry(config, entryName, entrypoints);
177176

178177
let mode: SSRMode | undefined;
179178
if (ssr) {
180179
mode = typeof ssr === 'object' ? ssr.mode || 'string' : 'string';
181180
}
182181

183-
if (mode === 'stream') {
182+
const ssrMode = resolveSSRMode({
183+
entry: entrypoint.entryName,
184+
config,
185+
appDirectory: appContext.appDirectory,
186+
});
187+
188+
if (ssrMode === 'stream') {
184189
const hasPageRoute = routes.some(
185190
route => 'type' in route && route.type === 'page',
186191
);
@@ -197,7 +202,7 @@ export const generateCode = async (
197202
code: await templates.fileSystemRoutes({
198203
metaName,
199204
routes: routes,
200-
ssrMode: useSSG ? 'string' : isUseRsc(config) ? 'stream' : mode,
205+
ssrMode,
201206
nestedRoutesEntry: entrypoint.nestedRoutesEntry,
202207
entryName: entrypoint.entryName,
203208
internalDirectory,
@@ -233,7 +238,7 @@ export const generateCode = async (
233238
const serverRoutesCode = await templates.fileSystemRoutes({
234239
metaName,
235240
routes: filtedRoutesForServer,
236-
ssrMode: useSSG ? 'string' : mode,
241+
ssrMode,
237242
nestedRoutesEntry: entrypoint.nestedRoutesEntry,
238243
entryName: entrypoint.entryName,
239244
internalDirectory,

0 commit comments

Comments
 (0)