feat: 검색엔진 색인 차단 및 보안 응답 헤더 추가
Co-Authored-By: Claude Opus 5
@beb4650f7e8a8b1f6fe681d72a692c71e93940ed
--- app/layout.tsx
+++ app/layout.tsx
... | ... | @@ -12,9 +12,24 @@ |
| 12 | 12 |
subsets: ["latin"], |
| 13 | 13 |
}); |
| 14 | 14 |
|
| 15 |
+// 관리자 사이트 전체(로그인 포함)의 검색엔진 색인을 차단한다 — 하위 라우트는 별도 설정이 |
|
| 16 |
+// 없는 한 이 값을 그대로 상속한다. robots.txt(app/robots.ts), next.config.ts의 |
|
| 17 |
+// X-Robots-Tag 헤더와 함께 SEO 차단 3층 구조를 이룬다. |
|
| 15 | 18 |
export const metadata: Metadata = {
|
| 16 |
- title: "Create Next App", |
|
| 17 |
- description: "Generated by create next app", |
|
| 19 |
+ title: "에듀페이 관리자", |
|
| 20 |
+ robots: {
|
|
| 21 |
+ index: false, |
|
| 22 |
+ follow: false, |
|
| 23 |
+ nocache: true, |
|
| 24 |
+ googleBot: {
|
|
| 25 |
+ index: false, |
|
| 26 |
+ follow: false, |
|
| 27 |
+ noimageindex: true, |
|
| 28 |
+ "max-image-preview": "none", |
|
| 29 |
+ "max-snippet": 0, |
|
| 30 |
+ "max-video-preview": 0, |
|
| 31 |
+ }, |
|
| 32 |
+ }, |
|
| 18 | 33 |
}; |
| 19 | 34 |
|
| 20 | 35 |
export default function RootLayout({
|
... | ... | @@ -24,7 +39,7 @@ |
| 24 | 39 |
}>) {
|
| 25 | 40 |
return ( |
| 26 | 41 |
<html |
| 27 |
- lang="en" |
|
| 42 |
+ lang="ko" |
|
| 28 | 43 |
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
| 29 | 44 |
> |
| 30 | 45 |
<body className="min-h-full flex flex-col">{children}</body>
|
+++ app/robots.ts
... | ... | @@ -0,0 +1,11 @@ |
| 1 | +import type { MetadataRoute } from 'next'; | |
| 2 | + | |
| 3 | +// 관리자 사이트 전체를 검색엔진 크롤링에서 차단한다. 로그인 페이지를 포함한 전 라우트 대상. | |
| 4 | +export default function robots(): MetadataRoute.Robots { | |
| 5 | + return { | |
| 6 | + rules: { | |
| 7 | + userAgent: '*', | |
| 8 | + disallow: '/', | |
| 9 | + }, | |
| 10 | + }; | |
| 11 | +} |
--- next.config.ts
+++ next.config.ts
... | ... | @@ -1,7 +1,32 @@ |
| 1 | 1 |
import type { NextConfig } from "next";
|
| 2 | 2 |
|
| 3 |
+// 다층 캐시·SEO 차단(§4)의 응답 헤더 계층. immutable 정적 자산(_next/static, _next/image)은 |
|
| 4 |
+// Next.js가 자체적으로 Cache-Control을 강제하므로 대상에서 제외해 자산 캐싱을 보존한다. |
|
| 5 |
+const SECURITY_HEADERS = [ |
|
| 6 |
+ {
|
|
| 7 |
+ key: "X-Robots-Tag", |
|
| 8 |
+ value: "noindex, nofollow, noarchive, nosnippet, noimageindex", |
|
| 9 |
+ }, |
|
| 10 |
+ { key: "Cache-Control", value: "no-store, no-cache, must-revalidate" },
|
|
| 11 |
+ { key: "Pragma", value: "no-cache" },
|
|
| 12 |
+ { key: "X-Content-Type-Options", value: "nosniff" },
|
|
| 13 |
+ { key: "X-Frame-Options", value: "DENY" },
|
|
| 14 |
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
| 15 |
+ {
|
|
| 16 |
+ key: "Permissions-Policy", |
|
| 17 |
+ value: "camera=(), microphone=(), geolocation=()", |
|
| 18 |
+ }, |
|
| 19 |
+]; |
|
| 20 |
+ |
|
| 3 | 21 |
const nextConfig: NextConfig = {
|
| 4 |
- /* config options here */ |
|
| 22 |
+ async headers() {
|
|
| 23 |
+ return [ |
|
| 24 |
+ {
|
|
| 25 |
+ source: "/((?!_next/static|_next/image).*)", |
|
| 26 |
+ headers: SECURITY_HEADERS, |
|
| 27 |
+ }, |
|
| 28 |
+ ]; |
|
| 29 |
+ }, |
|
| 5 | 30 |
}; |
| 6 | 31 |
|
| 7 | 32 |
export default nextConfig; |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?