feat(web): copy HTML snippet instead of full document for snippet components#3065
feat(web): copy HTML snippet instead of full document for snippet components#3065gabrielmfern wants to merge 4 commits intocanaryfrom
Conversation
…ponents
For all existing components (which are snippets), the HTML copy now
shows just the inner HTML fragment instead of the full email document.
The full document HTML (with Layout wrapper) is kept for preview iframes
and the Send feature.
For future full-document components, marking type as 'document' on the
Component interface will preserve the existing full-HTML-document behavior.
Changes:
- Add optional type field ('snippet' | 'document') to Component interface
- Generate htmlSnippet by rendering element without Layout wrapper and
stripping the DOCTYPE declaration and React hydration markers
- ComponentCodeView uses htmlSnippet when available (snippet components),
falls back to full html for document components
Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
…deQL Replace targeted React marker regexes with a single pattern that removes all HTML comments. This comprehensively handles React hydration markers (<!--hBHc->, <!--/hBHc->, etc.) and prevents the CodeQL 'Incomplete multi-character sanitization' alert. Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/src/app/components/get-imported-components-for.tsx">
<violation number="1" location="apps/web/src/app/components/get-imported-components-for.tsx:80">
P2: This strips required MSO conditional comments from copied snippets, so Outlook-specific email markup such as button spacing hacks is lost.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
… alert Replace the extractHtmlSnippet regex-stripping approach with React's renderToStaticMarkup, which produces pure static HTML with no DOCTYPE and no React hydration markers (<!--$-->, etc.). This eliminates the need to strip HTML comments entirely, removing the CodeQL 'Incomplete multi-character sanitization' alert at the root. Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/src/app/components/get-imported-components-for.tsx">
<violation number="1" location="apps/web/src/app/components/get-imported-components-for.tsx:81">
P1: This helper still returns full-document wrappers for snippet components that export `<Html>/<Body>` themselves.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…ppets Two fixes: 1. Build failure: Next.js App Router forbids static imports of react-dom/server in Server Components. Switch to a dynamic import (the same pattern used internally by @react-email/render) so the bundler does not flag it. 2. Review: if a snippet component happens to wrap its own output in <Html>/<Body>, extract the inner body content so the copied HTML stays an embeddable fragment rather than a full document. Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/src/app/components/get-imported-components-for.tsx">
<violation number="1" location="apps/web/src/app/components/get-imported-components-for.tsx:89">
P2: Extracting only `body` innerHTML drops attributes/styles set on a component’s own `<Body>` element.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const { renderToStaticMarkup } = await import('react-dom/server'); | ||
| const html = renderToStaticMarkup(element); | ||
| const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i); | ||
| return pretty((bodyMatch?.[1] ?? html).trim()); |
There was a problem hiding this comment.
P2: Extracting only body innerHTML drops attributes/styles set on a component’s own <Body> element.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/app/components/get-imported-components-for.tsx, line 89:
<comment>Extracting only `body` innerHTML drops attributes/styles set on a component’s own `<Body>` element.</comment>
<file context>
@@ -71,14 +70,23 @@ const getComponentCodeFrom = (fileContent: string): string => {
+ const { renderToStaticMarkup } = await import('react-dom/server');
+ const html = renderToStaticMarkup(element);
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
+ return pretty((bodyMatch?.[1] ?? html).trim());
};
</file context>
Summary
Previously, copying the HTML variant of any copy-paste component would copy the entire HTML email document (including
<!DOCTYPE>,<html>,<head>,<body>, and the Layout's Container wrapper). This made the HTML tab unhelpful for users who want to embed a component snippet into their own email.This PR makes the HTML copy behavior aware of whether a component is a snippet or a full document:
type: 'document'): copying HTML still gives the full HTML email document.Changes
apps/web/components/structure.tstype?: 'snippet' | 'document'field to theComponentinterface. Defaults to'snippet'for all existing components.apps/web/src/app/components/get-imported-components-for.tsxhtmlSnippet?: stringtoImportedComponent.code— present for snippet components, absent for document components.extractHtmlSnippethelper that strips the DOCTYPE declaration and React hydration markers from a rendered HTML document to produce a clean HTML fragment.<Layout>wrapper to get the raw component HTML, then stores it ascode.htmlSnippet.code.html(with Layout wrapper) is still generated for all components and is used by the preview iframes and Send feature.apps/web/src/components/component-code-view.tsxcode.htmlSnippetif present (snippet components), otherwise falls back to the fullcode.htmlwith the existingheight: 100vhstripping (document components).How it works
The
code.htmlfield always contains the full HTML document (used by preview iframes and the Send button). The newcode.htmlSnippetcontains only the rendered HTML of the component element itself — no<!DOCTYPE>,<html>,<head>,<body>, or Layout container wrappers. This is generated by rendering the component element directly without theLayoutwrapper, then stripping the DOCTYPE and React markers from the output.Slack Thread
Summary by cubic
Copying HTML for snippet components now returns only the inner HTML fragment, ready to paste into an existing email. Full-document components still copy the full HTML email.
New Features
type?: 'snippet' | 'document'(defaults to'snippet').code.htmlSnippetby rendering the component withoutLayoutusingrenderToStaticMarkup, producing clean HTML with no DOCTYPE or React markers.ComponentCodeViewuseshtmlSnippet; falls back to fullhtmlfor documents.Bug Fixes
react-dom/serverto avoid Next.js App Router build restrictions.<body>content.Written for commit 0639ee1. Summary will update on new commits.