diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPlaceholders-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPlaceholders-test.internal.js
new file mode 100644
index 0000000000..5f508e4b3f
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactDOMServerPlaceholders-test.internal.js
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
+
+let React;
+let ReactDOM;
+let ReactDOMServer;
+let ReactFeatureFlags;
+
+function initModules() {
+ // Reset warning cache.
+ jest.resetModuleRegistry();
+
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.enableSuspense = true;
+ ReactFeatureFlags.enableSuspenseServerRenderer = true;
+
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactDOMServer = require('react-dom/server');
+
+ // Make them available to the helpers.
+ return {
+ ReactDOM,
+ ReactDOMServer,
+ };
+}
+
+const {resetModules, serverRender} = ReactDOMServerIntegrationUtils(
+ initModules,
+);
+
+describe('ReactDOMServerPlaceholders', () => {
+ beforeEach(() => {
+ resetModules();
+ });
+
+ it('should always render the fallback when a placeholder is encountered', async () => {
+ const Suspended = props => {
+ throw new Promise(() => {});
+ };
+ const e = await serverRender(
+ }>
+
+ ,
+ );
+
+ expect(e.tagName).toBe('DIV');
+ });
+});
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index 230806536b..a8596b7c3d 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -23,12 +23,17 @@ import warningWithoutStack from 'shared/warningWithoutStack';
import checkPropTypes from 'prop-types/checkPropTypes';
import describeComponentFrame from 'shared/describeComponentFrame';
import ReactSharedInternals from 'shared/ReactSharedInternals';
-import {warnAboutDeprecatedLifecycles} from 'shared/ReactFeatureFlags';
+import {
+ warnAboutDeprecatedLifecycles,
+ enableSuspenseServerRenderer,
+} from 'shared/ReactFeatureFlags';
+
import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_ASYNC_MODE_TYPE,
+ REACT_PLACEHOLDER_TYPE,
REACT_PORTAL_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
@@ -911,6 +916,27 @@ class ReactDOMServerRenderer {
this.stack.push(frame);
return '';
}
+ case REACT_PLACEHOLDER_TYPE: {
+ if (enableSuspenseServerRenderer) {
+ const nextChildren = toArray(
+ // Always use the fallback when synchronously rendering to string.
+ ((nextChild: any): ReactElement).props.fallback,
+ );
+ const frame: Frame = {
+ type: null,
+ domNamespace: parentNamespace,
+ children: nextChildren,
+ childIndex: 0,
+ context: context,
+ footer: '',
+ };
+ if (__DEV__) {
+ ((frame: any): FrameDev).debugElementStack = [];
+ }
+ this.stack.push(frame);
+ return '';
+ }
+ }
// eslint-disable-next-line-no-fallthrough
default:
break;
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index c0e53946ea..e5b009653d 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -42,6 +42,9 @@ export const enableProfilerTimer = __PROFILE__;
// Track which interactions trigger each commit.
export const enableSchedulerTracking = __PROFILE__;
+// Only used in www builds.
+export const enableSuspenseServerRenderer = false;
+
// Only used in www builds.
export function addUserTimingListener() {
invariant(false, 'Not implemented.');
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js
index 634a9236aa..5b4a2eb37c 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js
@@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableSchedulerTracking = __PROFILE__;
+export const enableSuspenseServerRenderer = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js
index 210e40c449..3890fa12e6 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js
@@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableSchedulerTracking = __PROFILE__;
+export const enableSuspenseServerRenderer = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index f86dd8c5cf..07a943c9b9 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -27,6 +27,7 @@ export const enableUserTimingAPI = __DEV__;
export const warnAboutLegacyContextAPI = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableSchedulerTracking = __PROFILE__;
+export const enableSuspenseServerRenderer = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index 8097e29d95..08d6134447 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -22,6 +22,7 @@ export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = false;
export const enableProfilerTimer = __PROFILE__;
export const enableSchedulerTracking = __PROFILE__;
+export const enableSuspenseServerRenderer = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js
index 0d78dacdf2..eb37c87c25 100644
--- a/packages/shared/forks/ReactFeatureFlags.persistent.js
+++ b/packages/shared/forks/ReactFeatureFlags.persistent.js
@@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableSchedulerTracking = __PROFILE__;
+export const enableSuspenseServerRenderer = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 280359f999..63a06b31b3 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
export const enableSchedulerTracking = false;
+export const enableSuspenseServerRenderer = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 4fa93944f3..daeb9a80e9 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
export const enableSchedulerTracking = false;
+export const enableSuspenseServerRenderer = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 5baa9cdff0..258a0dd3b3 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -16,6 +16,7 @@ export const {
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
enableGetDerivedStateFromCatch,
+ enableSuspenseServerRenderer,
replayFailedUnitOfWorkWithInvokeGuardedCallback,
warnAboutDeprecatedLifecycles,
} = require('ReactFeatureFlags');