Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Successful in 53s
ci / 모바일 린트·타입·Jest (push) Successful in 45s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 21s
ci / .NET API 서버 테스트 (push) Successful in 13s
deploy-site / deploy (push) Failing after 27s
ci / 워크스페이스 빌드 검증 (push) Successful in 36s
Crawlers received an empty client-rendered shell (107 characters of text); most AI crawlers do not run JavaScript, so the product was invisible to them. - Build renders each locale to static HTML (/ for Korean, /en/ … /vi/) with src/entry-server.tsx + scripts/prerender.mjs; the browser hydrates the same locale. The language menu links to those pages instead of switching in place. - Per-locale head: title, description, canonical, hreflang (+ x-default /en/), Open Graph and X cards with a per-locale 1200x630 image. - JSON-LD graph: Organization, WebSite, WebPage, SoftwareApplication (KRW offers, version, release date, download URL from the canonical sources), HowTo and FAQPage. No invented ratings. - robots.txt (search, ai-input and ai-train allowed; /app, /api, invites excluded), sitemap.xml with language alternates, llms.txt and llms-full.txt generated from the same translations and canonical values. - IndexNow key and a post-deploy ping (Bing, Naver, Yandex). - A factual "at a glance" section and two FAQ answers (recognised languages, where data is stored) in all 10 languages. - Icons, apple-touch-icon and web manifest; asset base is now absolute so sub-path pages load the same bundle. Verified: 3.6k-7.7k characters of text per page, no hydration warnings, no overflow at 320/1440 in six locales, axe 0, Lighthouse mobile SEO, accessibility and best practices 100.
23 lines
1.3 KiB
JavaScript
23 lines
1.3 KiB
JavaScript
// scripts/ci/ping-indexnow.mjs
|
|
// 사이트 배포 뒤 IndexNow(Bing·네이버·Yandex 등 공유)에 바뀐 주소를 알린다. 계정이 필요 없고,
|
|
// 소유 확인은 사이트 루트의 키 파일(site/public/<key>.txt)로 한다. 주소 목록은 prerender 가 만든
|
|
// site/dist/indexnow-urls.json(sitemap 과 같은 목록)이다.
|
|
|
|
import { readdirSync, readFileSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
|
|
const HOST = 'd3ro.chanpaca.net'
|
|
const publicDir = join(process.cwd(), 'site', 'public')
|
|
const keyFile = readdirSync(publicDir).find((name) => /^[0-9a-f]{32}\.txt$/.test(name))
|
|
if (!keyFile) throw new Error('indexnow: site/public/<32 hex>.txt 키 파일이 없다')
|
|
const key = readFileSync(join(publicDir, keyFile), 'utf8').trim()
|
|
const urlList = JSON.parse(readFileSync(join(process.cwd(), 'site', 'dist', 'indexnow-urls.json'), 'utf8'))
|
|
|
|
const response = await fetch('https://api.indexnow.org/indexnow', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json; charset=utf-8' },
|
|
body: JSON.stringify({ host: HOST, key, keyLocation: `https://${HOST}/${keyFile}`, urlList }),
|
|
})
|
|
// 200/202 = 접수. 429 등은 배포 실패로 보지 않고 경고만 남긴다.
|
|
console.log(`[indexnow] ${response.status} ${response.statusText} (${urlList.length} urls)`)
|
|
if (response.status >= 400 && response.status !== 429) process.exitCode = 1
|