diff --git a/package.json b/package.json
index a32def7..1c1857a 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"clsx": "^2.1.1",
"lucide-react": "^0.544.0",
"next": "15.5.3",
+ "next-seo": "^6.8.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"tailwind-merge": "^3.3.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 191062e..2e0f6e7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -28,6 +28,9 @@ importers:
next:
specifier: 15.5.3
version: 15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ next-seo:
+ specifier: ^6.8.0
+ version: 6.8.0(next@15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react:
specifier: 19.1.0
version: 19.1.0
@@ -3011,6 +3014,16 @@ packages:
integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==,
}
+ next-seo@6.8.0:
+ resolution:
+ {
+ integrity: sha512-zcxaV67PFXCSf8e6SXxbxPaOTgc8St/esxfsYXfQXMM24UESUVSXFm7f2A9HMkAwa0Gqn4s64HxYZAGfdF4Vhg==,
+ }
+ peerDependencies:
+ next: ^8.1.1-canary.54 || >=9.0.0
+ react: ">=16.0.0"
+ react-dom: ">=16.0.0"
+
next@15.5.3:
resolution:
{
@@ -5843,6 +5856,12 @@ snapshots:
natural-compare@1.4.0: {}
+ next-seo@6.8.0(next@15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ next: 15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
next@15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
"@next/env": 15.5.3
diff --git a/src/app/lab/noindex/page.tsx b/src/app/lab/noindex/page.tsx
new file mode 100644
index 0000000..ca0284f
--- /dev/null
+++ b/src/app/lab/noindex/page.tsx
@@ -0,0 +1,14 @@
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Labs (Internal)",
+ robots: {
+ index: false,
+ follow: false,
+ googleBot: { index: false, follow: false, noimageindex: true, nosnippet: true },
+ },
+};
+
+export default function Page() {
+ return 내부 테스트 페이지(검색 제외);
+}
diff --git a/src/app/lab/og-preview/page.tsx b/src/app/lab/og-preview/page.tsx
new file mode 100644
index 0000000..022d31d
--- /dev/null
+++ b/src/app/lab/og-preview/page.tsx
@@ -0,0 +1,24 @@
+import { withBase } from "@/seo/baseUrl"; // 선택: 절대 URL 유틸
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "PlanMate 프로모션 Seed",
+ description: "공유 카드(OG/Twitter) 테스트용 페이지",
+ openGraph: {
+ title: "PlanMate 프로모션 Seed",
+ description: "공유 카드(OG/Twitter) 테스트용 페이지",
+ images: [
+ {
+ url: withBase("/og/promo-seed.png"),
+ width: 1200,
+ height: 630,
+ alt: "PlanMate 프로모션 Seed",
+ },
+ ],
+ },
+ twitter: { card: "summary_large_image" },
+};
+
+export default function Page() {
+ return OG/Twitter 카드 테스트 페이지;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index a5eaa6b..3c8ba87 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,10 +1,53 @@
+// app/layout.tsx
import type { Metadata } from "next";
import "./globals.css";
import { Providers } from "./providers";
+import {
+ DEFAULT_DESCRIPTION,
+ DEFAULT_TITLE,
+ LOCALE,
+ OG_DEFAULT_IMAGE,
+ SITE_NAME,
+ SITE_URL,
+ TITLE_TEMPLATE,
+} from "@/seo/constants";
+
export const metadata: Metadata = {
- title: "Custom Daily Planner",
- description: "나만의 맞춤형 플래너를 손쉽게 디자인하고 사용할 수 있는 웹 앱",
+ // 절대 URL 기준점 (canonical/OG 절대경로 변환에 사용)
+ metadataBase: new URL(SITE_URL),
+
+ // 전역 타이틀 규칙
+ title: {
+ default: DEFAULT_TITLE,
+ template: TITLE_TEMPLATE, // 예: "{페이지제목} | MyPlanMate"
+ },
+
+ // 전역 설명
+ description: DEFAULT_DESCRIPTION,
+
+ // Open Graph 기본값
+ openGraph: {
+ type: "website",
+ url: "/", // metadataBase 기준으로 절대 URL 처리
+ siteName: SITE_NAME,
+ title: DEFAULT_TITLE,
+ description: DEFAULT_DESCRIPTION,
+ images: [
+ {
+ url: OG_DEFAULT_IMAGE, // "/og/og-default.png" → 절대 URL로 자동 변환
+ width: 1200,
+ height: 630,
+ alt: `${SITE_NAME} 대표 이미지`,
+ },
+ ],
+ locale: LOCALE, // "ko_KR"
+ },
+
+ // Twitter 카드 기본값
+ twitter: {
+ card: "summary_large_image",
+ },
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
@@ -25,6 +68,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
*/}
+
{children}