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
This commit is contained in:
commit
548ec5c0c1
@ -23,6 +23,7 @@ import {
|
|||||||
collectJsonLd,
|
collectJsonLd,
|
||||||
homeMeta,
|
homeMeta,
|
||||||
readBakedLastmod,
|
readBakedLastmod,
|
||||||
|
readBakedNoindex,
|
||||||
readBakedTitle,
|
readBakedTitle,
|
||||||
renderHead,
|
renderHead,
|
||||||
renderLlmsTxt,
|
renderLlmsTxt,
|
||||||
@ -766,9 +767,11 @@ function writeRootMachineFiles(outRoot: string, origin: string) {
|
|||||||
.map((entry) => ({slug: entry.name, file: join(sitesDir, entry.name, 'index.html')}))
|
.map((entry) => ({slug: entry.name, file: join(sitesDir, entry.name, 'index.html')}))
|
||||||
// index.html 이 없으면 발행이 끝나지 않은(또는 실패한) 디렉토리다. 사이트맵에 넣지 않는다.
|
// index.html 이 없으면 발행이 끝나지 않은(또는 실패한) 디렉토리다. 사이트맵에 넣지 않는다.
|
||||||
.filter((entry) => existsSync(entry.file))
|
.filter((entry) => existsSync(entry.file))
|
||||||
.map((entry) => {
|
// 제목·lastmod·noindex 가 같은 HTML 에서 나온다 — 파일은 한 번만 읽는다.
|
||||||
// 제목과 lastmod 가 같은 HTML 에서 나온다 — 파일은 한 번만 읽는다.
|
.map((entry) => ({...entry, html: readFileSync(entry.file, 'utf-8')}))
|
||||||
const html = readFileSync(entry.file, 'utf-8');
|
// ★ noindex 를 선언한 페이지(목업·백업)는 싣지 않는다 — readBakedNoindex 주석 참조.
|
||||||
|
.filter((entry) => !readBakedNoindex(entry.html))
|
||||||
|
.map(({html, ...entry}) => {
|
||||||
return {
|
return {
|
||||||
// ★ 끝 슬래시를 붙이지 않는다. 페이지의 canonical 은 `/s/<slug>` 다(shared/lib/slug.ts
|
// ★ 끝 슬래시를 붙이지 않는다. 페이지의 canonical 은 `/s/<slug>` 다(shared/lib/slug.ts
|
||||||
// publishUrl). 사이트맵이 `/s/<slug>/` 로 어긋나 있던 동안 서치콘솔은 제출한 URL 을
|
// publishUrl). 사이트맵이 `/s/<slug>/` 로 어긋나 있던 동안 서치콘솔은 제출한 URL 을
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
import {describe, expect, it} from 'vitest';
|
import {describe, expect, it} from 'vitest';
|
||||||
|
|
||||||
import {MOONLIGHT_STAY_PAYLOAD} from '../fixtures/moonlight-stay';
|
import {MOONLIGHT_STAY_PAYLOAD} from '../fixtures/moonlight-stay';
|
||||||
import {readBakedLastmod, readBakedTitle} from './directory';
|
import {readBakedLastmod, readBakedNoindex, readBakedTitle} from './directory';
|
||||||
import {renderHead} from './head';
|
import {renderHead} from './head';
|
||||||
import {homeMeta} from './meta';
|
import {homeMeta} from './meta';
|
||||||
|
|
||||||
@ -44,3 +44,20 @@ describe('readBakedTitle', () => {
|
|||||||
expect(readBakedTitle('<html><head></head></html>')).toBe('');
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -20,6 +20,21 @@ export function readBakedTitle(html: string): string {
|
|||||||
return match ? match[1].trim() : '';
|
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` 를 그대로** 쓴다.
|
* 사이트맵 `lastmod` — **페이지가 스스로 선언한 `dateModified` 를 그대로** 쓴다.
|
||||||
*
|
*
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user