Skip to content

feat(examples): add interactive Next.js App Router example - #53

Merged
ivoIturrieta merged 3 commits into
unlayer:mainfrom
Cypher-Aura-19:feat/nextjs-app-router-example
Aug 13, 2026
Merged

feat(examples): add interactive Next.js App Router example#53
ivoIturrieta merged 3 commits into
unlayer:mainfrom
Cypher-Aura-19:feat/nextjs-app-router-example

Conversation

@Cypher-Aura-19

Copy link
Copy Markdown
Contributor

Summary

Closes #21.

Adds a focused Next.js 15 App Router example demonstrating server-side rendering of an Unlayer Elements email template using renderToHtml() and renderToPlainText().

Changes

  • Adds a realistic welcome email template built with @unlayer/react-elements.
  • Renders HTML and plain-text output exclusively on the server.
  • Adds an interactive preview with HTML, source and plain-text tabs.
  • Adds desktop and mobile viewport controls.
  • Adds dependency-free clipboard actions.
  • Provides /email-preview/raw for accessing the rendered HTML directly.
  • Includes complete setup and local package-development instructions.
  • Links the example from the root README.

Workspace compatibility

The example is isolated from the root workspace to prevent React 19 dependencies from affecting the repository’s React 18 packages. It consumes the published package like a normal external Next.js application.

The example README also explains how to test local package changes using a packed tarball.

Verification

  • pnpm install in the example: passed
  • pnpm build in the example: passed
  • pnpm dev: verified
  • /: returns 200
  • /email-preview: returns 200
  • /email-preview/raw: returns 200 with text/html; charset=utf-8
  • Root pnpm build: passed
  • Root tests: 538 tests passed

@ivoIturrieta ivoIturrieta left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this example. The production build and all routes work correctly, but I found a few issues to address before merging:

  1. The example pins next@15.5.3, which has a critical React Flight RCE and several high-severity vulnerabilities. pnpm audit --prod currently reports 33 vulnerabilities. Please upgrade to at least next@15.5.21, regenerate the lockfile, and rerun the audit.

  2. Running next build inside the example still causes Next.js to select the monorepo root because it detects both lockfiles. The build emits a workspace-root warning, and the output trace references the repository-root package.json. Please set outputFileTracingRoot to the example directory in next.config.ts.

  3. The root .gitignore now ignores every __snapshots__/ directory in the monorepo. This seems unrelated to the example and could silently hide committed test snapshots. Please remove it or scope it narrowly to the example.

- Upgrade Next.js from 15.5.3 to 15.5.22 (fixes 33 audit vulnerabilities)
- Add pnpm overrides for postcss>=8.5.18 and sharp>=0.35.0 to clear remaining transitive vulnerabilities
- Set outputFileTracingRoot to __dirname in next.config.ts to prevent workspace-root tracing warning
- .gitignore: no __snapshots__/ rule exists in the PR diff, left unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Cypher-Aura-19

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I’ve pushed ec69618 addressing the requested changes:

  • Upgraded the example from next@15.5.3 to next@15.5.22.
  • Regenerated the example lockfile.
  • Added outputFileTracingRoot: __dirname in the example next.config.ts.
  • Confirmed the example build no longer emits the workspace-root warning.
  • Ran pnpm audit --prod in the example and it now reports 0 vulnerabilities.

Verification:

  • pnpm install in the example passed
  • pnpm audit --prod in the example passed with 0 vulnerabilities
  • pnpm build in the example passed
  • root pnpm build passed
  • root pnpm test passed: 538 tests

For the .gitignore note: I checked the current PR diff against main, and this branch does not add a __snapshots__/ rule to the root or package .gitignore files. That may have applied to an earlier branch state, so I left .gitignore unchanged.

@ivoIturrieta

Copy link
Copy Markdown
Collaborator

Thanks — the previous review points are addressed: Next.js is upgraded, the output-tracing warning is gone, and no snapshot ignore rule was added.

I found one remaining blocker:

  1. The example is incompatible with the repository-pinned pnpm@9.7.0. From a clean checkout,
    running pnpm install inside the example fails with:

    ERR_PNPM_LOCKFILE_CONFIG_MISMATCH

    Forcing installation with --no-frozen-lockfile removes the postcss and sharp overrides
    from the lockfile and resolves vulnerable versions. pnpm audit --prod then reports five
    vulnerabilities, including three high-severity findings.

    The committed lockfile installs, builds, and audits successfully with pnpm 11. Please either pin
    a compatible pnpm version in the example.

Additional corrections:

  1. WelcomeEmail says its props may come from a DB, webhook, or CMS, but plan is interpolated
    into the raw html prop and dashboardUrl is passed through without validation. The package
    preserves injected markup and javascript: URLs, and the preview iframe is unsandboxed. The
    current constants are safe, but the example should escape dynamic HTML, validate URLs, and sandbox
    the iframe.

  2. The homepage says “No client JavaScript, no use client,” but PreviewDashboard.tsx is a
    client component. Please clarify that email construction and rendering are server-only while the
    interactive dashboard uses client JavaScript.

Move the postcss/sharp overrides into the package.json pnpm field and drop
the pnpm-11-only pnpm-workspace.yaml overrides + allowBuilds placeholder, so
a clean checkout installs with the repository-pinned pnpm@9.7.0 instead of
failing with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH. The regenerated lockfile
audits clean (0 vulnerabilities).

Escape the dynamic plan value and validate dashboardUrl as http(s) in
WelcomeEmail, and sandbox the preview iframe, so untrusted props cannot inject
markup or javascript: URLs.

Clarify on the homepage that email construction and rendering are server-only
while the interactive dashboard uses client JavaScript.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Cypher-Aura-19

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. Pushed 5f84f6a addressing all four points:

1. pnpm@9.7.0 compatibility (blocker). Root cause: the lockfile was generated with pnpm 11, which reads overrides from pnpm-workspace.yaml; the repo-pinned pnpm 9.7.0 reads them from the package.json pnpm field, so it saw no overrides and the frozen check failed with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH. Fix:

  • Moved the postcss/sharp overrides into the package.json pnpm field.
  • Added "packageManager": "pnpm@9.7.0" to the example.
  • Removed the pnpm-11-only overrides + allowBuilds placeholder from pnpm-workspace.yaml.
  • Regenerated the lockfile with pnpm 9.7.0.

Verified from a clean checkout with pnpm 9.7.0: pnpm install --frozen-lockfile passes, pnpm audit --prod reports 0 vulnerabilities, and pnpm build succeeds.

2. Untrusted props in WelcomeEmail. Added src/utils/safe.ts with escapeHtml() and requireSafeUrl(). plan is now escaped before it is interpolated into the raw html prop, and dashboardUrl is validated as an http(s) URL (rejecting javascript: and malformed input).

3. Unsandboxed preview iframe. Added sandbox="" to the preview iframe so the rendered email runs with no privileges. Confirmed the email still renders.

4. Homepage client/server claim. Reworded to clarify that email construction and rendering are server-only, while the interactive preview dashboard is the part that uses client JavaScript.

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.50%. Comparing base (adaa68c) to head (5f84f6a).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #53   +/-   ##
=======================================
  Coverage   95.50%   95.50%           
=======================================
  Files          31       31           
  Lines        1937     1937           
  Branches      382      382           
=======================================
  Hits         1850     1850           
  Misses         86       86           
  Partials        1        1           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ivoIturrieta
ivoIturrieta merged commit 63d7289 into unlayer:main Aug 13, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Example: Next.js App Router

2 participants