{ "name": "Rebuild a whole page as code (Tailwind, React, HTML or Vue) with MiroMiro", "nodes": [ { "id": "note", "name": "About this workflow", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -460, -120 ], "parameters": { "content": "## Rebuild a whole page as code\nMiroMiro rebuilds every section of the page in a real browser: header, hero, features, pricing, footer...\n\nYou get **one file per section in a zip** (on **Zip the code**) and a link to **the whole page assembled** (on **Get the whole-page link**), with a Download HTML button.\n\nIn **Settings**, `output` is `tailwind`, `jsx` (React), `html` or `vue`. 10 credits per section; repeat calls within 24h are free.\n\n**Setup (1 minute)**\n1. Get a free MiroMiro API key (300 credits a month, no card): https://miromiro.app/api/onboarding\n2. Open any MiroMiro node > Credential > Create new, paste the key.\n3. Change the website in **Settings**, then click **Execute workflow**.", "height": 340, "width": 760, "color": 7 } }, { "id": "run", "name": "Run", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ -420, 300 ], "parameters": {} }, { "id": "settings", "name": "Settings", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ -200, 300 ], "parameters": { "assignments": { "assignments": [ { "id": "s0", "name": "website", "value": "https://linear.app", "type": "string" }, { "id": "s1", "name": "output", "value": "tailwind", "type": "string" } ] }, "options": {} } }, { "id": "c1", "name": "Turn the hero into code", "type": "n8n-nodes-miromiro.miroMiro", "typeVersion": 1, "position": [ 20, 300 ], "credentials": { "miroMiroApi": { "id": "", "name": "MiroMiro API" } }, "parameters": { "resource": "website", "operation": "code", "url": "={{ $json.website }}", "framework": "={{ $json.output }}", "section": "hero", "codeOptions": {} } }, { "id": "c2", "name": "Split: one item per section", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 240, 300 ], "parameters": { "jsCode": "// The first result is the hero, and it lists every other section on the page.\nconst first = $input.first().json\nconst settings = $('Settings').first().json\nconst rest = (first.sections || [])\n .filter(s => s.selector !== first.selector)\n .sort((a, b) => a.order - b.order)\n .slice(0, 15)\nreturn rest.map(s => ({ json: { url: settings.website, selector: s.selector, section: s.label, order: s.order, output: settings.output }, pairedItem: { item: 0 } }))" } }, { "id": "c3", "name": "Turn each section into code", "type": "n8n-nodes-miromiro.miroMiro", "typeVersion": 1, "position": [ 460, 300 ], "credentials": { "miroMiroApi": { "id": "", "name": "MiroMiro API" } }, "parameters": { "resource": "website", "operation": "code", "url": "={{ $json.url }}", "framework": "={{ $json.output }}", "section": "hero", "codeOptions": { "selector": "={{ $json.selector }}" } }, "onError": "continueRegularOutput" }, { "id": "c4", "name": "Save each section as a file", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 680, 300 ], "parameters": { "jsCode": "// Hero plus every section, sorted by where they sit on the page.\nconst hero = $('Turn the hero into code').first().json\nconst order = Object.fromEntries((hero.sections || []).map(s => [s.selector, s.order]))\nconst labelOf = Object.fromEntries((hero.sections || []).map(s => [s.selector, String(s.label || '').split(/\\s+/).slice(0, 4).join(' ')]))\nconst all = [hero, ...$input.all().map(i => i.json)].filter(r => r.code)\n .sort((a, b) => (order[a.selector] ?? (a === hero ? 0 : 999)) - (order[b.selector] ?? (b === hero ? 0 : 999)))\nconst output = $('Settings').first().json.output\nconst ext = { tailwind: 'html', html: 'html', jsx: 'jsx', vue: 'vue' }[output] || 'txt'\n// File names from the first words of each section.\nconst slug = s => String(s || 'section').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40)\nconst binary = {}\nall.forEach((r, i) => {\n const name = String(i + 1).padStart(2, '0') + '-' + slug(r === hero ? 'hero' : labelOf[r.selector] || r.selector)\n const text = r.css && ext === 'html' ? '\\n' + r.code : r.code\n binary['file_' + i] = { data: Buffer.from(text).toString('base64'), mimeType: 'text/plain', fileName: name + '.' + ext, fileExtension: ext }\n})\nconst page = all.filter(r => r.pagePreviewUrl).pop()?.pagePreviewUrl\nreturn [{ json: { sections: all.length, pagePreviewUrl: page }, binary, pairedItem: { item: 0 } }]" } }, { "id": "c5", "name": "Zip the code", "type": "n8n-nodes-base.compression", "typeVersion": 1.1, "position": [ 900, 300 ], "parameters": { "operation": "compress", "binaryPropertyName": "={{ Object.keys($binary).join(\",\") }}", "outputFormat": "zip", "fileName": "={{ String($('Settings').first().json.website).split('/')[2].replace(/^www\\./, '') }}-code.zip", "binaryPropertyOutput": "data" } }, { "id": "c6", "name": "Get the whole-page link", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1120, 300 ], "parameters": { "jsCode": "// The whole page, every section assembled, with a Download HTML button.\nreturn [{ json: { pagePreviewUrl: $('Save each section as a file').first().json.pagePreviewUrl, sections: $('Save each section as a file').first().json.sections }, pairedItem: { item: 0 } }]" } } ], "connections": { "Run": { "main": [ [ { "node": "Settings", "type": "main", "index": 0 } ] ] }, "Settings": { "main": [ [ { "node": "Turn the hero into code", "type": "main", "index": 0 } ] ] }, "Turn the hero into code": { "main": [ [ { "node": "Split: one item per section", "type": "main", "index": 0 } ] ] }, "Split: one item per section": { "main": [ [ { "node": "Turn each section into code", "type": "main", "index": 0 } ] ] }, "Turn each section into code": { "main": [ [ { "node": "Save each section as a file", "type": "main", "index": 0 } ] ] }, "Save each section as a file": { "main": [ [ { "node": "Zip the code", "type": "main", "index": 0 } ] ] }, "Zip the code": { "main": [ [ { "node": "Get the whole-page link", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1" } }