mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-22 13:32:11 +00:00
*PR Created by the Glary-Bot Agent* --- ## Summary Adds "comfyui app" / "comfyui web app" / "comfy ui application" keywords to the titles and meta descriptions of the home, download, and Comfy Cloud pages (and zh-CN equivalents) to recover organic traffic for those queries. ## Context Organic traffic for the query **"comfyui app"** dropped after `https://docs.comfy.org/interface/app-mode` started outranking the product/landing pages. The docs page about app-mode converts worse than the product pages, so we want Google to prefer comfy.org product pages for that query. The cleanest, lowest-risk lever is on-page SEO metadata. ## Changes - **What**: - `apps/website/src/pages/index.astro` → title `ComfyUI App — Professional Control of Visual AI` + product-focused description. - `apps/website/src/pages/download.astro` → title `Download the ComfyUI App — Run Visual AI Locally` + desktop-app description. - `apps/website/src/pages/cloud/index.astro` → title `Comfy Cloud — The ComfyUI Web App` + web-app description. - `apps/website/src/pages/zh-CN/{index,download,cloud/index}.astro` → localised Chinese titles and descriptions so the zh-CN product pages no longer fall back to the English `BaseLayout` default. - `apps/website/src/layouts/BaseLayout.astro` → unchanged net-net (touched then reverted to neutral copy after review feedback so non-product / non-localised pages keep their existing, generic fallback). - **Breaking**: none. Visual content, routing, and components are untouched — only `<title>` and `<meta>` tags change. ## Review Focus - The keyword copy reads naturally (no stuffing) and stays under typical SERP truncation limits (≤ ~165 chars). - zh-CN pages get Chinese descriptions — they intentionally don't repeat the English keywords, since "comfyui app" is an English-language query. - Pre-existing behaviour preserved: zh-CN pages **without** an explicit description still inherit the English `BaseLayout` default. Fixing that fallback for the whole zh-CN tree is out of scope for this PR — happy to follow up if desired. ## Verification - `pnpm typecheck` — 0 errors - `pnpm build` — 39 pages built clean - `pnpm test:unit` — 23/23 pass - `pnpm format:check apps/website/src` — clean - Manually verified rendered `<title>` and `<meta name="description">` via Playwright on `/`, `/download`, `/cloud`, and the zh-CN equivalents. ## Screenshots Home page rendered with the new title (visible in browser tab / SERP preview); visual content unchanged. ## Screenshots  ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11834-feat-website-add-comfyui-app-SEO-keywords-to-product-pages-3546d73d3650819da11bd665c2fcfb88) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
161 lines
5.3 KiB
Plaintext
161 lines
5.3 KiB
Plaintext
---
|
|
import { ClientRouter } from 'astro:transitions'
|
|
import Analytics from '@vercel/analytics/astro'
|
|
import '../styles/global.css'
|
|
import type { Locale } from '../i18n/translations'
|
|
import SiteFooter from '../components/common/SiteFooter.vue'
|
|
import SiteNav from '../components/common/SiteNav.vue'
|
|
import { fetchGitHubStars, formatStarCount } from '../utils/github'
|
|
|
|
interface Props {
|
|
title: string
|
|
description?: string
|
|
keywords?: string[]
|
|
ogImage?: string
|
|
noindex?: boolean
|
|
}
|
|
|
|
const {
|
|
title,
|
|
description = 'Comfy is the AI creation engine for visual professionals who demand control.',
|
|
keywords,
|
|
ogImage = 'https://media.comfy.org/website/comfy.webp',
|
|
noindex = false,
|
|
} = Astro.props
|
|
|
|
const keywordsContent = keywords && keywords.length > 0 ? keywords.join(', ') : undefined
|
|
|
|
const siteBase = Astro.site ?? 'https://comfy.org'
|
|
const canonicalURL = new URL(Astro.url.pathname, siteBase)
|
|
const ogImageURL = new URL(ogImage, siteBase)
|
|
const rawLocale = Astro.currentLocale ?? 'en'
|
|
const locale: Locale = rawLocale === 'zh-CN' ? 'zh-CN' : 'en'
|
|
const rawStars = await fetchGitHubStars('Comfy-Org', 'ComfyUI')
|
|
const githubStars = rawStars ? formatStarCount(rawStars) : ''
|
|
|
|
const gtmId = 'GTM-NP9JM6K7'
|
|
const gtmEnabled = import.meta.env.PROD
|
|
|
|
const organizationJsonLd = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Organization',
|
|
name: 'Comfy Org',
|
|
url: 'https://comfy.org',
|
|
logo: 'https://comfy.org/icons/logomark.svg',
|
|
sameAs: [
|
|
'https://github.com/comfyanonymous/ComfyUI',
|
|
'https://discord.gg/comfyorg',
|
|
'https://x.com/comaboratory',
|
|
'https://reddit.com/r/comfyui',
|
|
'https://linkedin.com/company/comfyorg',
|
|
'https://instagram.com/comfyorg',
|
|
],
|
|
}
|
|
|
|
const websiteJsonLd = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'WebSite',
|
|
name: 'Comfy',
|
|
url: 'https://comfy.org',
|
|
}
|
|
---
|
|
|
|
<!doctype html>
|
|
<html lang={locale} class="overflow-x-clip">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta name="description" content={description} />
|
|
{keywordsContent && <meta name="keywords" content={keywordsContent} />}
|
|
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
|
<title>{title}</title>
|
|
|
|
<link rel="icon" href="/icons/logomark.svg" type="image/svg+xml" />
|
|
<link rel="canonical" href={canonicalURL.href} />
|
|
<link rel="preconnect" href="https://www.googletagmanager.com" />
|
|
<link rel="dns-prefetch" href="https://www.googletagmanager.com" />
|
|
|
|
<!-- Open Graph -->
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:title" content={title} />
|
|
<meta property="og:description" content={description} />
|
|
<meta property="og:image" content={ogImageURL.href} />
|
|
<meta property="og:image:width" content="1200" />
|
|
<meta property="og:image:height" content="630" />
|
|
<meta property="og:url" content={canonicalURL.href} />
|
|
<meta property="og:locale" content={locale} />
|
|
<meta property="og:site_name" content="Comfy" />
|
|
|
|
<!-- Twitter -->
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:site" content="@comaboratory" />
|
|
<meta name="twitter:title" content={title} />
|
|
<meta name="twitter:description" content={description} />
|
|
<meta name="twitter:image" content={ogImageURL.href} />
|
|
|
|
<!-- Structured Data -->
|
|
<script is:inline type="application/ld+json" set:html={JSON.stringify(organizationJsonLd)} />
|
|
<script is:inline type="application/ld+json" set:html={JSON.stringify(websiteJsonLd)} />
|
|
|
|
<!-- Google Tag Manager -->
|
|
{gtmEnabled && (
|
|
<script is:inline define:vars={{ gtmId }}>
|
|
;(function (w, d, s, l, i) {
|
|
w[l] = w[l] || []
|
|
w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' })
|
|
var f = d.getElementsByTagName(s)[0],
|
|
j = d.createElement(s),
|
|
dl = l != 'dataLayer' ? '&l=' + l : ''
|
|
j.async = true
|
|
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl
|
|
f.parentNode.insertBefore(j, f)
|
|
})(window, document, 'script', 'dataLayer', gtmId)
|
|
</script>
|
|
)}
|
|
|
|
<ClientRouter />
|
|
<slot name="head" />
|
|
</head>
|
|
<body class="bg-primary-comfy-ink text-white font-formula antialiased overflow-x-clip">
|
|
{gtmEnabled && (
|
|
<noscript>
|
|
<iframe
|
|
src={`https://www.googletagmanager.com/ns.html?id=${gtmId}`}
|
|
height="0"
|
|
width="0"
|
|
style="display:none;visibility:hidden"
|
|
></iframe>
|
|
</noscript>
|
|
)}
|
|
|
|
<SiteNav locale={locale} github-stars={githubStars} client:load />
|
|
<main class="mt-20 lg:mt-32">
|
|
<slot />
|
|
</main>
|
|
<SiteFooter locale={locale} client:load />
|
|
|
|
<Analytics />
|
|
|
|
<script>
|
|
import { initSmoothScroll, cancelScroll } from '../scripts/smoothScroll'
|
|
import { ScrollTrigger } from '../scripts/gsapSetup'
|
|
import { initPostHog, capturePageview } from '../scripts/posthog'
|
|
|
|
initSmoothScroll()
|
|
|
|
if (import.meta.env.PROD) {
|
|
initPostHog()
|
|
document.addEventListener('astro:page-load', capturePageview)
|
|
}
|
|
|
|
document.addEventListener('astro:page-load', () => {
|
|
ScrollTrigger.refresh()
|
|
})
|
|
|
|
document.addEventListener('astro:before-preparation', () => {
|
|
cancelScroll()
|
|
})
|
|
</script>
|
|
</body>
|
|
</html>
|