diff --git a/README.ko.md b/README.ko.md
index a43224f..6cc3fa9 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -115,21 +115,24 @@ function MyPage() {
### ReactHeadSafeProps
-| Prop | Type | Description |
-| ---------------- | -------- | ------------------------------------------------------------------------ |
-| `title` | `string` | `document.title`에 설정될 페이지 제목 |
-| `description` | `string` | SEO를 위한 메타 설명 태그 콘텐츠 |
-| `keywords` | `string` | SEO를 위한 메타 키워드 태그 콘텐츠 |
-| `ogTitle` | `string` | 소셜 미디어 공유를 위한 Open Graph 제목 (og:title) |
-| `ogDescription` | `string` | 소셜 미디어 공유를 위한 Open Graph 설명 (og:description) |
-| `ogImage` | `string` | 소셜 미디어 공유를 위한 Open Graph 이미지 URL (og:image) |
-| `ogUrl` | `string` | 소셜 미디어 공유를 위한 Open Graph URL (og:url) |
-| `ogType` | `OgType` | Open Graph 타입 (og:type). 표준 12개 값 자동완성 + 임의 string 모두 허용 |
-| `canonicalUrl` | `string` | SEO를 위한 페이지의 대표 URL (``) |
-| `ogSiteName` | `string` | 소셜 미디어 공유를 위한 사이트 이름 (og:site_name) |
-| `ogLocale` | `string` | 콘텐츠의 언어/지역 코드 (og:locale), 예: `"ko_KR"`, `"en_US"` |
-| `twitterSite` | `string` | 웹사이트의 Twitter 계정 (twitter:site), 예: `"@mysite"` |
-| `twitterCreator` | `string` | 콘텐츠 작성자의 Twitter 계정 (twitter:creator), 예: `"@author"` |
+| Prop | Type | Description |
+| ---------------- | -------- | -------------------------------------------------------------------------------------------- |
+| `title` | `string` | `document.title`에 설정될 페이지 제목 |
+| `description` | `string` | SEO를 위한 메타 설명 태그 콘텐츠 |
+| `keywords` | `string` | SEO를 위한 메타 키워드 태그 콘텐츠 |
+| `ogTitle` | `string` | 소셜 미디어 공유를 위한 Open Graph 제목 (og:title) |
+| `ogDescription` | `string` | 소셜 미디어 공유를 위한 Open Graph 설명 (og:description) |
+| `ogImage` | `string` | 소셜 미디어 공유를 위한 Open Graph 이미지 URL (og:image) |
+| `ogUrl` | `string` | 소셜 미디어 공유를 위한 Open Graph URL (og:url) |
+| `ogType` | `OgType` | Open Graph 타입 (og:type). 표준 12개 값 자동완성 + 임의 string 모두 허용 |
+| `canonicalUrl` | `string` | SEO를 위한 페이지의 대표 URL (``) |
+| `ogSiteName` | `string` | 소셜 미디어 공유를 위한 사이트 이름 (og:site_name) |
+| `ogLocale` | `string` | 콘텐츠의 언어/지역 코드 (og:locale), 예: `"ko_KR"`, `"en_US"` |
+| `twitterSite` | `string` | 웹사이트의 Twitter 계정 (twitter:site), 예: `"@mysite"` |
+| `twitterCreator` | `string` | 콘텐츠 작성자의 Twitter 계정 (twitter:creator), 예: `"@author"` |
+| `robots` | `string` | 크롤러 색인을 제어하는 robots 메타 태그 콘텐츠, 예: `"noindex,follow"`, `"noindex,nofollow"` |
+
+> **주의:** JS로 주입된 `robots="noindex"`는 JavaScript를 렌더링하는 크롤러(예: Googlebot, 단 지연 있음)만 인식합니다. JS를 실행하지 않는 크롤러(상당수의 봇, 소셜 미디어 프리뷰 봇 등)는 이 태그를 무시하므로 페이지가 여전히 색인될 수 있습니다. 반드시 확실하게 색인에서 제외해야 하는 페이지라면, 서버 측 `X-Robots-Tag: noindex` 헤더를 사용하세요.
### Twitter Card 지원
diff --git a/README.md b/README.md
index 649d65c..e226d27 100644
--- a/README.md
+++ b/README.md
@@ -118,21 +118,24 @@ That's it! The component will automatically:
### ReactHeadSafeProps
-| Prop | Type | Description |
-| ---------------- | -------- | ------------------------------------------------------------------------------------------------ |
-| `title` | `string` | The page title that will be set in the `document.title` |
-| `description` | `string` | The meta description tag content for SEO |
-| `keywords` | `string` | The meta keywords tag content for SEO |
-| `ogTitle` | `string` | The Open Graph title (og:title) for social media sharing |
-| `ogDescription` | `string` | The Open Graph description (og:description) for social media sharing |
-| `ogImage` | `string` | The Open Graph image URL (og:image) for social media sharing |
-| `ogUrl` | `string` | The canonical URL of your object that will be used as its permanent ID in the graph (og:url) |
-| `ogType` | `OgType` | The type of your object (og:type). Autocompletes 12 standard OG values; also accepts any string. |
-| `canonicalUrl` | `string` | The canonical URL of the page for SEO (``) |
-| `ogSiteName` | `string` | The site name for social media sharing (og:site_name) |
-| `ogLocale` | `string` | The locale of the content (og:locale), e.g. `"en_US"`, `"ko_KR"` |
-| `twitterSite` | `string` | The Twitter @username of the website (twitter:site), e.g. `"@mysite"` |
-| `twitterCreator` | `string` | The Twitter @username of the content author (twitter:creator), e.g. `"@author"` |
+| Prop | Type | Description |
+| ---------------- | -------- | ------------------------------------------------------------------------------------------------------- |
+| `title` | `string` | The page title that will be set in the `document.title` |
+| `description` | `string` | The meta description tag content for SEO |
+| `keywords` | `string` | The meta keywords tag content for SEO |
+| `ogTitle` | `string` | The Open Graph title (og:title) for social media sharing |
+| `ogDescription` | `string` | The Open Graph description (og:description) for social media sharing |
+| `ogImage` | `string` | The Open Graph image URL (og:image) for social media sharing |
+| `ogUrl` | `string` | The canonical URL of your object that will be used as its permanent ID in the graph (og:url) |
+| `ogType` | `OgType` | The type of your object (og:type). Autocompletes 12 standard OG values; also accepts any string. |
+| `canonicalUrl` | `string` | The canonical URL of the page for SEO (``) |
+| `ogSiteName` | `string` | The site name for social media sharing (og:site_name) |
+| `ogLocale` | `string` | The locale of the content (og:locale), e.g. `"en_US"`, `"ko_KR"` |
+| `twitterSite` | `string` | The Twitter @username of the website (twitter:site), e.g. `"@mysite"` |
+| `twitterCreator` | `string` | The Twitter @username of the content author (twitter:creator), e.g. `"@author"` |
+| `robots` | `string` | The robots meta tag content controlling crawler indexing, e.g. `"noindex,follow"`, `"noindex,nofollow"` |
+
+> **Warning:** A JS-injected `robots="noindex"` is only honored by crawlers that render JavaScript (e.g. Googlebot, with delay). Non-JS crawlers (many bots, social preview crawlers) ignore it, so the page may still be indexed. For pages that must be reliably excluded from indexing, use a server-side `X-Robots-Tag: noindex` header instead.
### Twitter Card Support
diff --git a/package.json b/package.json
index 931311e..c9d8ee2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-head-safe",
- "version": "1.9.0",
+ "version": "1.10.0",
"description": "A lightweight React head manager for CSR apps. Safely manage document title, meta tags, Open Graph, and SEO metadata without duplicates. TypeScript support included.",
"author": "umsungjun",
"license": "MIT",
diff --git a/src/ReactHeadSafe.tsx b/src/ReactHeadSafe.tsx
index 36a908b..edd4f79 100644
--- a/src/ReactHeadSafe.tsx
+++ b/src/ReactHeadSafe.tsx
@@ -33,6 +33,7 @@ export const ReactHeadSafe: FC = ({
ogLocale,
twitterSite,
twitterCreator,
+ robots,
}) => {
useLayoutEffect(() => {
// Track selectors for meta/link tags inserted in this effect run.
@@ -125,6 +126,12 @@ export const ReactHeadSafe: FC = ({
insertedSelectors.push('meta[name="twitter:creator"]');
}
+ // Update robots meta tag (controls crawler indexing, e.g. "noindex,follow")
+ if (robots !== undefined) {
+ updateMetaTag('name', 'robots', robots);
+ insertedSelectors.push('meta[name="robots"]');
+ }
+
// Cleanup: remove every tag this effect inserted.
// - On deps change: runs before the next effect, clearing stale tags
// (handles prop → undefined transitions).
@@ -148,6 +155,7 @@ export const ReactHeadSafe: FC = ({
ogLocale,
twitterSite,
twitterCreator,
+ robots,
]);
return null;
diff --git a/src/test/ReactHeadSafe.test.tsx b/src/test/ReactHeadSafe.test.tsx
index 2fa8729..1023f53 100644
--- a/src/test/ReactHeadSafe.test.tsx
+++ b/src/test/ReactHeadSafe.test.tsx
@@ -634,6 +634,51 @@ describe('ReactHeadSafe', () => {
});
});
+ describe('robots meta tag', () => {
+ it('should create robots meta tag', () => {
+ render();
+
+ const metaTag = document.querySelector('meta[name="robots"]');
+ expect(metaTag).toBeInTheDocument();
+ expect(metaTag?.getAttribute('content')).toBe('noindex,follow');
+ });
+
+ it('should update robots meta tag when prop changes', () => {
+ const { rerender } = render();
+ rerender();
+
+ const metaTag = document.querySelector('meta[name="robots"]');
+ expect(metaTag?.getAttribute('content')).toBe('noindex,nofollow');
+ });
+
+ it('should prevent duplicate robots meta tags', () => {
+ const { rerender } = render();
+ rerender();
+
+ const metaTags = document.querySelectorAll('meta[name="robots"]');
+ expect(metaTags).toHaveLength(1);
+ expect(metaTags[0].getAttribute('content')).toBe('noindex,nofollow');
+ });
+
+ it('should remove robots meta tag on unmount', () => {
+ const { unmount } = render();
+ unmount();
+
+ expect(
+ document.querySelector('meta[name="robots"]')
+ ).not.toBeInTheDocument();
+ });
+
+ it('should remove robots meta tag when prop becomes undefined', () => {
+ const { rerender } = render();
+ rerender();
+
+ expect(
+ document.querySelector('meta[name="robots"]')
+ ).not.toBeInTheDocument();
+ });
+ });
+
describe('cleanup on unmount', () => {
it('should remove all inserted meta and link tags on unmount', () => {
const { unmount } = render(
diff --git a/src/types.ts b/src/types.ts
index ecd9d3b..898ccac 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -48,4 +48,12 @@ export interface ReactHeadSafeProps {
twitterSite?: string;
/** The Twitter @username of the content author (twitter:creator), e.g. "@author" */
twitterCreator?: string;
+ /**
+ * The robots meta tag content controlling crawler indexing, e.g. "noindex,follow", "noindex,nofollow".
+ *
+ * Note: a JS-injected `noindex` is only honored by crawlers that render JavaScript
+ * (e.g. Googlebot, with delay). Non-JS crawlers ignore it. For pages that must be
+ * reliably excluded, use a server-side `X-Robots-Tag: noindex` header instead.
+ */
+ robots?: string;
}