feat(website): add SEO, sitemap, redirects, CI workflow, and Vercel config#10156
feat(website): add SEO, sitemap, redirects, CI workflow, and Vercel config#10156christian-byrne wants to merge 2 commits intowebsite/04-secondary-pagesfrom
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Comment |
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
🎭 Playwright: ✅ 592 passed, 0 failed · 6 flaky📊 Browser Reports
|
🎨 Storybook: ✅ Built — View Storybook |
…onfig - seo-01: Add Organization + WebSite JSON-LD structured data to BaseLayout - seo-03: Add @astrojs/sitemap integration, robots.txt (14 URLs generated) - seo-04: Add redirects: /pricing, /enterprise, /blog, /contact, /press - deploy-01: Add vercel.json for Vercel project configuration - deploy-02: Add ci-website-build.yaml GitHub Actions workflow - OG/Twitter meta already present in BaseLayout from Wave 2 (seo-02)
59ad4d7 to
7ddc7c0
Compare
52f7c97 to
183da54
Compare
📦 Bundle Size
⚡ Performance ReportNo baseline found — showing absolute values.
Raw data{
"timestamp": "2026-03-17T14:34:47.905Z",
"gitSha": "b28bf45ddc1a3da0c25dc37f3e72a070db0ad8d0",
"branch": "website/05-seo-polish",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2031.1360000000036,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.339,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 361.63900000000007,
"heapDeltaBytes": 1981076,
"domNodes": 20,
"jsHeapTotalBytes": 15466496,
"scriptDurationMs": 20.213,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "canvas-idle",
"durationMs": 2026.6369999999938,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.634999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 364.541,
"heapDeltaBytes": 1448188,
"domNodes": 22,
"jsHeapTotalBytes": 17563648,
"scriptDurationMs": 21.845000000000002,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-idle",
"durationMs": 2048.78199999996,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.294,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 354.421,
"heapDeltaBytes": 1612776,
"domNodes": 20,
"jsHeapTotalBytes": 17563648,
"scriptDurationMs": 20.782000000000004,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1952.274000000017,
"styleRecalcs": 79,
"styleRecalcDurationMs": 48.07899999999999,
"layouts": 12,
"layoutDurationMs": 4.632000000000001,
"taskDurationMs": 863.4759999999999,
"heapDeltaBytes": -1659912,
"domNodes": 66,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 144.195,
"eventListeners": 30,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1844.5539999999596,
"styleRecalcs": 77,
"styleRecalcDurationMs": 38.262,
"layouts": 12,
"layoutDurationMs": 3.6850000000000005,
"taskDurationMs": 765.85,
"heapDeltaBytes": -2668532,
"domNodes": 60,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 132.528,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1772.7150000000051,
"styleRecalcs": 75,
"styleRecalcDurationMs": 36.868,
"layouts": 12,
"layoutDurationMs": 3.2909999999999995,
"taskDurationMs": 755.15,
"heapDeltaBytes": -3221904,
"domNodes": 58,
"jsHeapTotalBytes": 17825792,
"scriptDurationMs": 133.55999999999997,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1764.363000000003,
"styleRecalcs": 29,
"styleRecalcDurationMs": 16.86,
"layouts": 6,
"layoutDurationMs": 0.755,
"taskDurationMs": 306.95399999999995,
"heapDeltaBytes": 6126688,
"domNodes": 77,
"jsHeapTotalBytes": 17301504,
"scriptDurationMs": 26.723,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1767.078999999967,
"styleRecalcs": 31,
"styleRecalcDurationMs": 15.814000000000002,
"layouts": 6,
"layoutDurationMs": 0.6579999999999999,
"taskDurationMs": 295.86600000000004,
"heapDeltaBytes": 6145588,
"domNodes": 77,
"jsHeapTotalBytes": 17301504,
"scriptDurationMs": 24.687,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1745.346999999981,
"styleRecalcs": 32,
"styleRecalcDurationMs": 16.991,
"layouts": 6,
"layoutDurationMs": 0.5239999999999999,
"taskDurationMs": 308.29,
"heapDeltaBytes": 6507144,
"domNodes": 79,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 23.149,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "dom-widget-clipping",
"durationMs": 614.8570000000007,
"styleRecalcs": 14,
"styleRecalcDurationMs": 10.892,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 367.60900000000004,
"heapDeltaBytes": 13129812,
"domNodes": 23,
"jsHeapTotalBytes": 15466496,
"scriptDurationMs": 75.282,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.65999999999999
},
{
"name": "dom-widget-clipping",
"durationMs": 587.551000000019,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.695,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 354.782,
"heapDeltaBytes": 13730336,
"domNodes": 20,
"jsHeapTotalBytes": 12845056,
"scriptDurationMs": 70.713,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.65999999999999
},
{
"name": "dom-widget-clipping",
"durationMs": 612.7139999999827,
"styleRecalcs": 15,
"styleRecalcDurationMs": 11.236999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 355.75100000000003,
"heapDeltaBytes": 13580740,
"domNodes": 26,
"jsHeapTotalBytes": 13369344,
"scriptDurationMs": 66.50399999999999,
"eventListeners": 26,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "large-graph-idle",
"durationMs": 2032.4269999999842,
"styleRecalcs": 11,
"styleRecalcDurationMs": 12.987000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 530.858,
"heapDeltaBytes": -10337084,
"domNodes": 23,
"jsHeapTotalBytes": 9056256,
"scriptDurationMs": 109.70600000000002,
"eventListeners": 28,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "large-graph-idle",
"durationMs": 2010.423000000003,
"styleRecalcs": 11,
"styleRecalcDurationMs": 11.277000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 485.90000000000003,
"heapDeltaBytes": -10397812,
"domNodes": 23,
"jsHeapTotalBytes": 8507392,
"scriptDurationMs": 95.447,
"eventListeners": 30,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "large-graph-idle",
"durationMs": 2021.6519999999605,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.859999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 500.2989999999999,
"heapDeltaBytes": -10026536,
"domNodes": 22,
"jsHeapTotalBytes": 9056256,
"scriptDurationMs": 95.20500000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000073
},
{
"name": "large-graph-pan",
"durationMs": 2099.3690000000242,
"styleRecalcs": 68,
"styleRecalcDurationMs": 16.53,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1127.4109999999998,
"heapDeltaBytes": -203816,
"domNodes": 16,
"jsHeapTotalBytes": 11329536,
"scriptDurationMs": 433.28,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "large-graph-pan",
"durationMs": 2132.729999999981,
"styleRecalcs": 71,
"styleRecalcDurationMs": 17.619999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1015.593,
"heapDeltaBytes": 6534604,
"domNodes": 22,
"jsHeapTotalBytes": 10543104,
"scriptDurationMs": 395.738,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "large-graph-pan",
"durationMs": 2070.945999999992,
"styleRecalcs": 68,
"styleRecalcDurationMs": 15.227000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1015.1119999999997,
"heapDeltaBytes": 1872064,
"domNodes": 16,
"jsHeapTotalBytes": 10285056,
"scriptDurationMs": 383.815,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000073
},
{
"name": "minimap-idle",
"durationMs": 2018.0420000000368,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.297999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 512.134,
"heapDeltaBytes": -10705620,
"domNodes": 20,
"jsHeapTotalBytes": 8245248,
"scriptDurationMs": 97.54599999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "minimap-idle",
"durationMs": 2016.9399999999769,
"styleRecalcs": 9,
"styleRecalcDurationMs": 9.629999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 477.843,
"heapDeltaBytes": -11976752,
"domNodes": 18,
"jsHeapTotalBytes": 6410240,
"scriptDurationMs": 87.64899999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "minimap-idle",
"durationMs": 2001.6290000000367,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.521,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 473.29799999999994,
"heapDeltaBytes": -11209636,
"domNodes": 20,
"jsHeapTotalBytes": 9056256,
"scriptDurationMs": 89.519,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 560.3990000000181,
"styleRecalcs": 48,
"styleRecalcDurationMs": 12.759000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 370.442,
"heapDeltaBytes": 12496500,
"domNodes": 22,
"jsHeapTotalBytes": 15728640,
"scriptDurationMs": 126.41000000000003,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 555.6670000000281,
"styleRecalcs": 48,
"styleRecalcDurationMs": 12.232999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 350.032,
"heapDeltaBytes": 13099736,
"domNodes": 22,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 124.28200000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 553.9039999999886,
"styleRecalcs": 48,
"styleRecalcDurationMs": 11.354,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 357.478,
"heapDeltaBytes": -11497116,
"domNodes": 22,
"jsHeapTotalBytes": 22282240,
"scriptDurationMs": 118.27799999999999,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000027
},
{
"name": "subgraph-idle",
"durationMs": 1994.416000000001,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.136999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 341.02299999999997,
"heapDeltaBytes": 862908,
"domNodes": 22,
"jsHeapTotalBytes": 17039360,
"scriptDurationMs": 17.720999999999997,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "subgraph-idle",
"durationMs": 2005.6060000000002,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.924999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 347.024,
"heapDeltaBytes": 858992,
"domNodes": 22,
"jsHeapTotalBytes": 17039360,
"scriptDurationMs": 17.815,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-idle",
"durationMs": 1998.3899999999721,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.623999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 350.777,
"heapDeltaBytes": 817792,
"domNodes": 14,
"jsHeapTotalBytes": 17039360,
"scriptDurationMs": 18.638999999999996,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 2007.2999999999865,
"styleRecalcs": 90,
"styleRecalcDurationMs": 51.733000000000004,
"layouts": 16,
"layoutDurationMs": 4.423,
"taskDurationMs": 967.1940000000001,
"heapDeltaBytes": -7026544,
"domNodes": 78,
"jsHeapTotalBytes": 17825792,
"scriptDurationMs": 108.55600000000001,
"eventListeners": 30,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.680000000000017
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1771.09999999999,
"styleRecalcs": 77,
"styleRecalcDurationMs": 40.049,
"layouts": 16,
"layoutDurationMs": 4.5649999999999995,
"taskDurationMs": 709.0970000000001,
"heapDeltaBytes": -7095164,
"domNodes": 65,
"jsHeapTotalBytes": 17039360,
"scriptDurationMs": 98.081,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1982.9859999999826,
"styleRecalcs": 87,
"styleRecalcDurationMs": 49.47899999999999,
"layouts": 16,
"layoutDurationMs": 4.539000000000001,
"taskDurationMs": 958.2649999999999,
"heapDeltaBytes": -7112480,
"domNodes": 72,
"jsHeapTotalBytes": 17563648,
"scriptDurationMs": 108.849,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "workflow-execution",
"durationMs": 448.8839999999641,
"styleRecalcs": 17,
"styleRecalcDurationMs": 28.944000000000006,
"layouts": 6,
"layoutDurationMs": 2.028,
"taskDurationMs": 140.21699999999998,
"heapDeltaBytes": 4709680,
"domNodes": 164,
"jsHeapTotalBytes": 4194304,
"scriptDurationMs": 34.125,
"eventListeners": 55,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.65999999999999
},
{
"name": "workflow-execution",
"durationMs": 427.59699999999157,
"styleRecalcs": 19,
"styleRecalcDurationMs": 24.717999999999996,
"layouts": 5,
"layoutDurationMs": 1.4529999999999998,
"taskDurationMs": 120.67500000000003,
"heapDeltaBytes": 4298788,
"domNodes": 157,
"jsHeapTotalBytes": 4456448,
"scriptDurationMs": 26.050000000000004,
"eventListeners": 55,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000027
},
{
"name": "workflow-execution",
"durationMs": 435.46099999997523,
"styleRecalcs": 19,
"styleRecalcDurationMs": 24.054,
"layouts": 5,
"layoutDurationMs": 1.435,
"taskDurationMs": 116.71199999999999,
"heapDeltaBytes": 4315700,
"domNodes": 156,
"jsHeapTotalBytes": 4456448,
"scriptDurationMs": 25.865000000000002,
"eventListeners": 55,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.65999999999999
}
]
} |
viva-jinyi
left a comment
There was a problem hiding this comment.
SEO Suggestions (non-blocking)
Nice additions — sitemap, robots.txt, and JSON-LD cover the essentials. Here are a few low-effort improvements to consider:
1. OG image dimensions — BaseLayout.astro
Adding explicit width/height lets social crawlers render the preview layout without downloading the image first.
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />2. Twitter site tag — BaseLayout.astro
Links shared cards back to the Comfy account automatically.
<meta name="twitter:site" content="@comaboratory" />3. External link rel consistency — SiteFooter.vue
Social icons already have target="_blank" rel="noopener noreferrer", but the column links (Comfy Cloud, ComfyHub, Docs, Blog, GitHub) are external URLs without them. This is a tabnabbing vector and inconsistent with the social links.
<a
v-for="link in column.links"
:key="link.href"
:href="link.href"
:target="link.href.startsWith('http') ? '_blank' : undefined"
:rel="link.href.startsWith('http') ? 'noopener noreferrer' : undefined"
>4. Preconnect hint for GTM — BaseLayout.astro
Saves a DNS lookup + TLS handshake before the GTM script loads, helps LCP.
<link rel="preconnect" href="https://www.googletagmanager.com" />
<link rel="dns-prefetch" href="https://www.googletagmanager.com" />5. Per-page noindex prop — BaseLayout.astro
Pages like Terms of Service and Privacy Policy don't need indexing. Currently there's no way to control this per-page.
interface Props {
title: string
description?: string
ogImage?: string
noindex?: boolean
}{noindex && <meta name="robots" content="noindex, nofollow" />}
dante01yoon
left a comment
There was a problem hiding this comment.
Description is empty?
| redirects: { | ||
| '/pricing': '/cloud/pricing', | ||
| '/enterprise': '/cloud/enterprise', | ||
| '/blog': 'https://blog.comfy.org/', |
There was a problem hiding this comment.
Suggestion: Astro generates HTML page with for this. That's a client-side redirect — worse for SEO than a proper HTTP 301. Since the site is deployed to Vercel, all redirects (especially external ones) would be
better served from vercel.json where they become edge-level 301s:
"redirects": [
{ "source": "/blog", "destination": "https://blog.comfy.org/", "permanent": true },
{ "source": "/pricing", "destination": "/cloud/pricing", "permanent": true },
// ...
]This is ironic given the PR's purpose is SEO improvement? would better moving all redirects to vercel.json.

Summary
Changes
Review Focus
Screenshots (if applicable)
┆Issue is synchronized with this Notion page by Unito