Repository: single-spa/single-spa-layout
Pull request #68 (2020-09-16 21:53:52)

Fix double-rendering of applications and fragments in renderServerResponseBody
If you look at the snapshots, the bug is pretty clear. We should not actually call `renderApplication` for the `<application>` elements **within** the `<single-spa-router>` element, since the single-spa-router element needs to remain unchanged so that the browser can understand it. Note that single-spa-layout dynamically injects `<application>` elements outside of the single-spa-router element that are where the server rendered content goes.


Patch:
diff --git a/src/server/renderServerResponseBody.js b/src/server/renderServerResponseBody.js
index 34fc309..489c63c 100644
--- a/src/server/renderServerResponseBody.js
+++ b/src/server/renderServerResponseBody.js
@@ -33,6 +33,7 @@ const GT_REGEX = />/g;
  * renderOptions: RenderOptions;
  * serverLayout: import('./constructServerLayout').serverLayout;
  * applicationProps: import('single-spa').AppProps;
+ * inRouterElement: boolean;
  * }} SerializeArgs
  *
  * @typedef {{
@@ -63,6 +64,7 @@ export function renderServerResponseBody(serverLayout, renderOptions) {
     renderOptions,
     serverLayout,
     applicationProps,
+    inRouterElement: false,
   });
 
   return {
@@ -76,7 +78,7 @@ export function renderServerResponseBody(serverLayout, renderOptions) {
  * @param {SerializeArgs} serializeArgs
  */
 function serializeChildNodes(args) {
-  const { node: parentNode } = args;
+  const { node: parentNode, inRouterElement } = args;
 
   let childNodes = treeAdapter.getChildNodes(parentNode);
 
@@ -88,7 +90,7 @@ function serializeChildNodes(args) {
     }
     let serialize;
 
-    if (isApplicationNode(node)) {
+    if (!inRouterElement && isApplicationNode(node)) {
       serialize = serializeApplication;
     } else if (isRouteNode(node)) {
       serialize = serializeRoute;
@@ -253,7 +255,8 @@ function serializeElement(args) {
         ? treeAdapter.getTemplateContent(node)
         : node;
 
-    const newArgs = { ...args, node: childNodesHolder };
+    const inRouterElement = args.inRouterElement || tn === "single-spa-router";
+    const newArgs = { ...args, node: childNodesHolder, inRouterElement };
     serializeChildNodes(newArgs);
     output.add(stringStream(`</${tn}>`));
   }


Test patch:
diff --git a/test/node-only/__snapshots__/renderServerResponseBody-node.test.js.snap b/test/node-only/__snapshots__/renderServerResponseBody-node.test.js.snap
index c2277ea..f4a4a35 100644
--- a/test/node-only/__snapshots__/renderServerResponseBody-node.test.js.snap
+++ b/test/node-only/__snapshots__/renderServerResponseBody-node.test.js.snap
@@ -33,14 +33,14 @@ exports[`renderServerResponseBody allows for strings to be returned from renderA
     <single-spa-router mode="history"
                        base="/"
     >
-      <div id="single-spa-application:undefined">
-      </div>
+      <application name="header">
+      </application>
       <div class="main-content">
         <button>
           Top level content
         </button>
-        <div id="single-spa-application:undefined">
-        </div>
+        <application name="app1">
+        </application>
         Some raw text
       </div>
       <span>
@@ -88,14 +88,14 @@ exports[`renderServerResponseBody dom-elements.html fixture renders the app1 rou
     <single-spa-router mode="history"
                        base="/"
     >
-      <div id="single-spa-application:undefined">
-      </div>
+      <application name="header">
+      </application>
       <div class="main-content">
         <button>
           Top level content
         </button>
-        <div id="single-spa-application:undefined">
-        </div>
+        <application name="app1">
+        </application>
         Some raw text
       </div>
       <span>