Compare commits

...

2 Commits

Author SHA1 Message Date
548ec5c0c1 Merge branch 'fix/sitemap-noindex' — 사이트맵·목록에서 noindex 페이지 제외
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEQ9auj65yJKk2MnWbtRqU
2026-09-11 14:09:16 +09:00
4216a05fca [fix] site: 사이트맵·목록·llms.txt 에서 noindex 페이지 제외 — 목업 복제본이 /s/stay 를 검색에서 밀어낸 것
/s/stay 가 "스테이머뭄" 구글 검색에서 통째로 빠졌다. out/s/ 에 목업·백업(stay2 · stay3 ·
stay.old)이 운영본과 제목·본문이 같은 채(단어 87%) 각자 자기를 canonical 로 가리키고 있었고,
사이트맵·/s 목록이 디렉토리를 훑어 이들을 전부 구글에 제출했다 — 같은 글 여러 벌 중 구글이
하나만 고른다. 목업 셋은 운영 볼륨에서 noindex 로 바꿨고(2026-09-11), 이 커밋은 그런 페이지가
다시 제출되지 않게 한다.

- seo/directory.ts: readBakedNoindex — 구운 HTML 의 robots 메타를 읽는다. 슬러그 이름(.old)으로
  거르지 않는다 — 페이지 자신의 선언이 유일한 출처다
- prerender.ts writeRootMachineFiles: noindex 페이지를 사이트맵·/s 목록·루트 llms.txt 에서 뺀다.
  남겨 두면 서치콘솔이 "제출된 URL 에 noindex" 를 계속 띄운다

vitest directory.test.ts 8 passed · tsc·eslint 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEQ9auj65yJKk2MnWbtRqU
2026-09-11 14:08:48 +09:00
3 changed files with 39 additions and 4 deletions

View File

@ -23,6 +23,7 @@ import {
collectJsonLd,
homeMeta,
readBakedLastmod,
readBakedNoindex,
readBakedTitle,
renderHead,
renderLlmsTxt,
@ -766,9 +767,11 @@ function writeRootMachineFiles(outRoot: string, origin: string) {
.map((entry) => ({slug: entry.name, file: join(sitesDir, entry.name, 'index.html')}))
// index.html 이 없으면 발행이 끝나지 않은(또는 실패한) 디렉토리다. 사이트맵에 넣지 않는다.
.filter((entry) => existsSync(entry.file))
.map((entry) => {
// 제목과 lastmod 가 같은 HTML 에서 나온다 — 파일은 한 번만 읽는다.
const html = readFileSync(entry.file, 'utf-8');
// 제목·lastmod·noindex 가 같은 HTML 에서 나온다 — 파일은 한 번만 읽는다.
.map((entry) => ({...entry, html: readFileSync(entry.file, 'utf-8')}))
// ★ noindex 를 선언한 페이지(목업·백업)는 싣지 않는다 — readBakedNoindex 주석 참조.
.filter((entry) => !readBakedNoindex(entry.html))
.map(({html, ...entry}) => {
return {
// ★ 끝 슬래시를 붙이지 않는다. 페이지의 canonical 은 `/s/<slug>` 다(shared/lib/slug.ts
// publishUrl). 사이트맵이 `/s/<slug>/` 로 어긋나 있던 동안 서치콘솔은 제출한 URL 을

View File

@ -10,7 +10,7 @@
import {describe, expect, it} from 'vitest';
import {MOONLIGHT_STAY_PAYLOAD} from '../fixtures/moonlight-stay';
import {readBakedLastmod, readBakedTitle} from './directory';
import {readBakedLastmod, readBakedNoindex, readBakedTitle} from './directory';
import {renderHead} from './head';
import {homeMeta} from './meta';
@ -44,3 +44,20 @@ describe('readBakedTitle', () => {
expect(readBakedTitle('<html><head></head></html>')).toBe('');
});
});
describe('readBakedNoindex', () => {
it('발행본 head 는 index 다 — 사이트맵에 남는다', () => {
expect(readBakedNoindex(bakedHead())).toBe(false);
});
it('noindex 를 선언한 목업은 걸러진다', () => {
const mockup =
'<meta name="robots" content="noindex, follow, max-snippet:-1, max-image-preview:large" />';
expect(readBakedNoindex(mockup)).toBe(true);
expect(readBakedNoindex('<meta name="robots" content="none" />')).toBe(true);
});
it('robots 메타가 없으면 색인 대상으로 친다', () => {
expect(readBakedNoindex('<html><head><title>x</title></head></html>')).toBe(false);
});
});

View File

@ -20,6 +20,21 @@ export function readBakedTitle(html: string): string {
return match ? match[1].trim() : '';
}
/**
* `noindex` . ·`/s` ·llms.txt .
*
* `out/s/` payload ·(`stay2` · `stay3` · `*.old`) ,
* . (`/s/stay`) · canonical
* , (2026-09-11)
* "스테이머뭄" . noindex
* "제출된 URL 에 noindex" .
* (`.old` ) .
*/
export function readBakedNoindex(html: string): boolean {
const match = /<meta name="robots" content="([^"]*)"/.exec(html);
return match ? /(^|[\s,])(noindex|none)([\s,]|$)/.test(match[1]) : false;
}
/**
* `lastmod` ** `dateModified` ** .
*