Document Workflow

Migrate a static HTML view to Pug without carrying over unsafe behavior

A practical workflow for inventorying a legacy HTML view, converting it to Pug, resolving active-code and parser warnings, defining template data, and testing the rendered result.

Written and tested by Published: Reviewed:

How this workflow was checked

Using Markup Conversion Workbench, we reproduced “Replace a global save handler with an explicit route and client contract” from the exact sample input. The result comparison covered “Capture the current render and behavior as a baseline” and “Convert the source and investigate every warning”; the linked test evidence was then checked for “Keeping inline events because Pug accepts them” and “Using unescaped Pug interpolation for convenience”.

The Pug result retained the POST action, label, required name field, and escaped local value, while the global onclick code remained a named migration task rather than executable output.

Problem

A legacy HTML page often relies on global scripts, inline onclick strings, document IDs, relative assets, server-injected values, form endpoints, and CSS loaded by a parent layout. Mechanical conversion can preserve the visible tree while carrying unsafe URLs and obsolete behavior into valid Pug. It can also hide changes made by the HTML parser or encourage developers to invent template logic before defining its data source. A safe migration separates syntax conversion from behavior redesign and proves the final render inside the real application.

When to use this

  • A reviewed static or server-rendered HTML view must move into an Express application that uses Pug.
  • One card, form, table, navigation section, email preview, or SVG fragment needs a Pug draft before application data is connected.
  • A legacy template audit needs to expose inline events, active URLs, scripts, malformed nesting, and page-level dependencies.
  • A team wants a repeatable checklist for migrating several HTML views without treating generated templates as sanitized or finished code.

Steps

  1. Step 1

    Choose one view and inventory everything it depends on

    Select the smallest coherent view or fragment that completes one user task. Record parent layouts, stylesheets, scripts, fonts, images, relative links, form actions, IDs queried by code, browser storage, global variables, server values, authentication assumptions, and error states. Remove secrets, customer data, private hosts, and production tokens from the conversion fixture. Decide whether the destination owns a complete document or an included fragment before retaining doctype, html, head, and body.

  2. Step 2

    Capture the current render and behavior as a baseline

    Render the source in its working environment and note heading order, labels, alt text, tab order, keyboard activation, form validation, loading and failure states, narrow-screen layout, and network requests. Save representative assertions or screenshots. Inspect the browser-parsed DOM as well as the original file because malformed HTML may already be normalized. This baseline defines what must remain and what legacy behavior should be intentionally removed.

  3. Step 3

    Convert the source and investigate every warning

    Open HTML to Pug in Markup Conversion Workbench and convert the reviewed source. Compare tags, nesting, text spaces, comments, attributes, SVG, template content, and document wrappers. Investigate parser-normalization notices, removed formatting whitespace, legacy doctypes, script or style elements, noscript blocks, inline on-event attributes, javascript or active data URLs, and template-looking markers. Syntax-validated output is only a migration draft; do not suppress a warning because the first render looks similar.

  4. Step 4

    Replace preserved active behavior with an application design

    Remove inline event strings and global DOM calls. Move behavior into named client modules or framework components with explicit inputs, error handling, and tests. Replace javascript URLs with approved navigation, validate form destinations, and decide whether scripts belong in the layout, a bundled client entry, or nowhere. Apply a defined URL and content trust policy. If source content is not fully trusted, sanitize it at the ingestion boundary rather than relying on conversion.

  5. Step 5

    Define Pug locals and introduce dynamic syntax deliberately

    List every value the route supplies and define its type, required status, default, escaping rule, and empty or error state. Add variables, conditions, loops, includes, mixins, and inheritance only where this contract requires them. Keep user-controlled values escaped, avoid unescaped interpolation without a documented sanitizer, and test missing or adversarial values. Move stable layout into shared templates only after the standalone render matches the baseline.

  6. Step 6

    Compile, render, and verify the destination view

    Run the project's Pug compile, formatter, linter, tests, and production build. Parse or snapshot the rendered HTML rather than snapshotting only Pug source. Exercise normal, empty, long, Unicode, invalid, loading, and failure data; test routes, assets, forms, keyboard use, accessible names, focus, 390px mobile layout, Content Security Policy, and expected network requests. Review the source-to-render diff and active-code resolutions before merging or publishing.

Example

Replace a global save handler with an explicit route and client contract

Input

<form class="profile" action="/profiles" method="post"><label for="name">Name</label><input id="name" name="name" value="Ada" required><button onclick="trackSave()" type="submit">Save</button></form>

Output

Convert the structure, remove the preserved onclick attribute, supply the current name through an escaped Pug local, keep a real label and POST action, and attach approved analytics from the application layer. Test missing and long names, validation failure, keyboard submission, CSRF handling, server errors, and the rendered form under the production CSP.

Common mistakes

Migrating an entire site export as one template

A full export combines unrelated views, metadata, scripts, and page chrome. Convert one route or included fragment at a time so ownership, warnings, and dependencies stay reviewable.

Keeping inline events because Pug accepts them

Valid attribute syntax does not make a global event string maintainable or safe. Replace it with application-owned behavior and test the interaction and failure path.

Using unescaped Pug interpolation for convenience

Unescaped output can turn data into executable markup. Keep user-controlled values escaped and require a documented sanitizer and trust boundary for any intentional raw HTML.

Adding loops and inheritance before defining locals

Template features without a data contract produce hidden undefined states and duplicated assumptions. Define route inputs, defaults, and errors first, then add the smallest required Pug logic.

Testing Pug source but not rendered HTML

A parser-valid file can render broken paths, duplicate IDs, inaccessible forms, unsafe URLs, or missing data. Compile with representative locals and inspect the final DOM, behavior, and network activity.

FAQ

Should a complete HTML document stay complete in Pug?

Only when the destination route owns the whole response. For an include or child template, remove document wrappers and let the parent layout own doctype, head metadata, scripts, and shared navigation.

Can I keep script and onclick content from the converted output?

Keep it only as review evidence. Move approved behavior into the application's client code, replace active URLs, and verify CSP and event behavior rather than carrying global strings forward automatically.

Why are text values emitted as static equals expressions?

Static string expressions preserve quotes, Unicode, whitespace, and Pug-looking interpolation markers without turning source text into template logic. Intended dynamic values should be introduced later through an explicit locals contract.

Does local conversion make untrusted HTML safe?

No. Local processing avoids uploading the source and does not execute it during conversion, but active attributes and elements may be preserved. Untrusted content needs a defined sanitizer, URL policy, escaping rules, and CSP at the application boundary.

What proves that the migration is complete?

Dependencies and locals are documented, every warning has a resolution, the destination build passes, representative rendered HTML matches the intended baseline, and behavior, accessibility, mobile layout, assets, forms, security, and network checks are clean.