diff --git a/packages/qwik/src/core/tests/render-api.spec.tsx b/packages/qwik/src/core/tests/render-api.spec.tsx
index fc48a7fe972..e6ffe3ad3af 100644
--- a/packages/qwik/src/core/tests/render-api.spec.tsx
+++ b/packages/qwik/src/core/tests/render-api.spec.tsx
@@ -437,6 +437,43 @@ describe('render api', () => {
'(window.qwikevents||(window.qwikevents=[]))'
);
});
+ it('should not render inside template', async () => {
+ const bigText = 'hello world '.repeat(3000); // ~30kB of text
+ const result = await renderToStringAndSetPlatform(
+
+
+
+
+
+
+
+ {bigText}
+ {bigText}
+
+ |
+
+
+ Before here is safe |
+
+
+
+
,
+ {
+ containerTagName: 'div',
+ qwikLoader: 'inline',
+ }
+ );
+ const document = createDocument({ html: result.html });
+ expect(document.querySelectorAll('script[id=qwikloader]')).toHaveLength(1);
+ const notQwikLoaderScriptElement = document.body.firstChild?.lastChild
+ ?.previousSibling as HTMLElement;
+ expect(notQwikLoaderScriptElement?.id).not.toEqual('qwikloader');
+ // qwik events should still be the last script of body
+ const eventsScriptElement = document.body.lastChild as HTMLElement;
+ expect(eventsScriptElement.textContent).toContain(
+ '(window.qwikevents||(window.qwikevents=[]))'
+ );
+ });
});
it('should support never render', async () => {
const result = await renderToStringAndSetPlatform(, {
diff --git a/packages/qwik/src/server/ssr-container.ts b/packages/qwik/src/server/ssr-container.ts
index 0e2bb3b5f2a..730806853cd 100644
--- a/packages/qwik/src/server/ssr-container.ts
+++ b/packages/qwik/src/server/ssr-container.ts
@@ -380,6 +380,8 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
return this.closeElement();
}
+ private $noScriptHere$ = 0;
+
/** Renders opening tag for DOM element */
openElement(
elementName: string,
@@ -387,9 +389,15 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
constAttrs?: SsrAttrs | null,
currentFile?: string | null
): string | undefined {
- if (this.qlInclude === QwikLoaderInclude.Inline && this.size > 30 * 1024) {
- // We waited long enough, on slow connections the page is already partially visible
- this.emitQwikLoaderInline();
+ if (this.qlInclude === QwikLoaderInclude.Inline) {
+ if (this.$noScriptHere$ === 0 && this.size > 30 * 1024) {
+ // We waited long enough, on slow connections the page is already partially visible
+ this.emitQwikLoaderInline();
+ }
+ // keep track of noscript and template
+ else if (elementName === 'noscript' || elementName === 'template') {
+ this.$noScriptHere$++;
+ }
}
let innerHTML: string | undefined = undefined;
@@ -482,6 +490,12 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
this.write('>');
}
this.lastNode = null;
+ if (this.qlInclude === QwikLoaderInclude.Inline) {
+ // keep track of noscript and template
+ if (elementName === 'noscript' || elementName === 'template') {
+ this.$noScriptHere$--;
+ }
+ }
}
/** Writes opening data to vNodeData for fragment boundaries */
diff --git a/packages/qwik/src/server/tag-nesting.ts b/packages/qwik/src/server/tag-nesting.ts
index dde7787fb90..b1a35036682 100644
--- a/packages/qwik/src/server/tag-nesting.ts
+++ b/packages/qwik/src/server/tag-nesting.ts
@@ -223,6 +223,8 @@ function isInTable(text: string): TagNesting {
case 'tbody':
case 'tfoot':
return TagNesting.TABLE_BODY;
+ case 'script':
+ return TagNesting.TEXT;
default:
return TagNesting.NOT_ALLOWED;
}
@@ -232,6 +234,8 @@ function isInTableBody(text: string): TagNesting {
switch (text) {
case 'tr':
return TagNesting.TABLE_ROW;
+ case 'script':
+ return TagNesting.TEXT;
default:
return TagNesting.NOT_ALLOWED;
}
@@ -242,6 +246,8 @@ function isInTableRow(text: string): TagNesting {
case 'td':
case 'th':
return TagNesting.ANYTHING;
+ case 'script':
+ return TagNesting.TEXT;
default:
return TagNesting.NOT_ALLOWED;
}
@@ -251,6 +257,8 @@ function isInTableColGroup(text: string): TagNesting {
switch (text) {
case 'col':
return TagNesting.EMPTY;
+ case 'script':
+ return TagNesting.TEXT;
default:
return TagNesting.NOT_ALLOWED;
}
@@ -262,6 +270,8 @@ function isInPicture(text: string): TagNesting {
return TagNesting.EMPTY;
case 'img':
return TagNesting.EMPTY;
+ case 'script':
+ return TagNesting.TEXT;
default:
return TagNesting.NOT_ALLOWED;
}
@@ -331,7 +341,6 @@ function isInPhrasing(text: string, allowInput: boolean): TagNesting {
case 'ruby':
case 's':
case 'samp':
- case 'script':
case 'select':
case 'slot':
case 'small':
@@ -346,6 +355,7 @@ function isInPhrasing(text: string, allowInput: boolean): TagNesting {
case 'video':
case 'wbr':
return allowInput ? TagNesting.PHRASING_ANY : TagNesting.PHRASING_INSIDE_INPUT;
+ case 'script':
case 'style':
return TagNesting.TEXT;
case 'picture':