Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .husky/commit-msg
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pnpm commitlint --edit "$1" || {
echo
echo "โŒ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ทœ์น™ ์œ„๋ฐ˜!"
echo " ํ˜•์‹: CDP-์ˆซ์ž type์ด๋ชจ์ง€(scope): subject"
echo " ํ˜•์‹: CDP-์ˆซ์ž type์ด๋ชจ์ง€ (scope): subject"
echo " ์˜ˆ์‹œ: CDP-31 fix๐Ÿ›(ci): CI error resolution"
echo " ํ—ˆ์šฉ: featโœจ, fix๐Ÿ›, refactor๐Ÿ”จ, style๐ŸŽจ, choreโš™๏ธ, docs๐Ÿ“, test๐Ÿงช"
exit 1
Expand Down
5 changes: 2 additions & 3 deletions commitlint.config.cjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/** commitlint.config.cjs */
module.exports = {
extends: ["@commitlint/config-conventional"],
parserPreset: {
parserOpts: {
// CDP-123 choreโš™๏ธ(scope): subject
// CDP-123 choreโš™๏ธ (scope): subject
headerPattern:
/^(CDP-\d+)\s(featโœจ|fix๐Ÿ›|refactor๐Ÿ”จ|style๐ŸŽจ|choreโš™๏ธ|docs๐Ÿ“|test๐Ÿงช)\(([^)]+)\):\s(.+)$/,
/^(CDP-\d+)\s(featโœจ|fix๐Ÿ›|refactor๐Ÿ”จ|style๐ŸŽจ|choreโš™๏ธ|docs๐Ÿ“|test๐Ÿงช)\s\(([^)]+)\):\s(.+)$/,
headerCorrespondence: ["ticket", "type", "scope", "subject"],
},
},
Expand Down
48 changes: 48 additions & 0 deletions next-sitemap.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const RAW_SITE_URL = process.env.SITE_URL ?? "https://myplanmate.vercel.app";
const siteUrl = RAW_SITE_URL.replace(/\/+$/, ""); // ๋ ์Šฌ๋ž˜์‹œ ์ œ๊ฑฐ

// "/" ์ด์™ธ ๊ฒฝ๋กœ์˜ ๋ ์Šฌ๋ž˜์‹œ ์ œ๊ฑฐ
const strip = (p) => (p !== "/" && p.endsWith("/") ? p.slice(0, -1) : p);

const config = {
siteUrl,
generateRobotsTxt: true,
outDir: "public",
sitemapSize: 5000,

// ๊ธฐ์กด exclude ๊ทธ๋Œ€๋กœ ์œ ์ง€
exclude: ["/api/*", "/admin/*", "/debug", "/lab/*"],

// <loc>์ด canonical๊ณผ 1:1๋กœ ๋™์ผํ•˜๋„๋ก ์ •๊ทœํ™”
transform: async (cfg, path) => {
const loc = strip(path);

// ์šฐ์„ ์ˆœ์œ„ ๊ทœ์น™(๋„ค ๋กœ์ง ๋ณด์กด)
const priority = loc === "/" ? 1.0 : loc.startsWith("/blog") ? 0.8 : (cfg.priority ?? 0.7);

return {
loc, // โœ… canonical๊ณผ ๋™์ผํ•œ ๋ฌธ์ž์—ด
changefreq: "daily",
priority,
lastmod: new Date().toISOString(),

// ์–ธ์–ด๋ณ„ ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฒฝ๋กœ ๋‹จ์œ„๋กœ alternateRefs ๋งคํ•‘
// ์˜ˆ) /todos โ†’ /ko/todos, /en/todos
alternateRefs: [
{ href: `${siteUrl}/ko${loc === "/" ? "" : loc}`, hreflang: "ko" },
{ href: `${siteUrl}/en${loc === "/" ? "" : loc}`, hreflang: "en" },
],
};
},

// robots.txt (๋„ค ๋กœ์ง ๋ณด์กด)
robotsTxtOptions: {
policies: [
{ userAgent: "*", allow: "/" },
{ userAgent: "*", disallow: ["/api/", "/admin/", "/debug", "/lab/"] },
],
additionalSitemaps: [`${siteUrl}/server-sitemap.xml`], // ๋™์  sitemap
},
};

export default config;
34 changes: 26 additions & 8 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// next.config.ts
import type { NextConfig } from "next";

/** ๋Œ€ํ‘œ ๋„๋ฉ”์ธ (์—†์œผ๋ฉด Vercel ํ”„๋ฆฌ๋ทฐ ๊ธฐ๋ณธ๊ฐ’) */
const RAW_SITE_URL = process.env.SITE_URL ?? "https://myplanmate.vercel.app";
const url = new URL(RAW_SITE_URL);
const NON_WWW_HOST = url.hostname.replace(/^www\./, "");
const WWW_HOST = url.hostname.startsWith("www.") ? url.hostname : `www.${NON_WWW_HOST}`;
const DEST_ORIGIN = `${url.protocol}//${NON_WWW_HOST}`;

const nextConfig: NextConfig = {
reactStrictMode: true,
poweredByHeader: false, // x-powered-by: Next.js ํ—ค๋” ์ œ๊ฑฐ
Expand Down Expand Up @@ -29,14 +37,8 @@ const nextConfig: NextConfig = {
{
source: "/:path*",
headers: [
{
key: "X-Frame-Options",
value: "DENY", // ํด๋ฆญ์žฌํ‚น ๋ฐฉ์ง€
},
{
key: "X-Content-Type-Options",
value: "nosniff", // MIME ํƒ€์ž… ์Šค๋‹ˆํ•‘ ๋ฐฉ์ง€
},
{ key: "X-Frame-Options", value: "DENY" }, // ํด๋ฆญ์žฌํ‚น ๋ฐฉ์ง€
{ key: "X-Content-Type-Options", value: "nosniff" }, // MIME ํƒ€์ž… ์Šค๋‹ˆํ•‘ ๋ฐฉ์ง€
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin", // ์•ˆ์ „ํ•œ referrer ์ „์†ก
Expand All @@ -49,6 +51,22 @@ const nextConfig: NextConfig = {
},
];
},

// โœ… 3) ์ค‘๋ณต ์ฃผ์†Œ ์ •๊ทœํ™” (์ถ”๊ฐ€)
async redirects() {
return [
// A) ํŠธ๋ ˆ์ผ๋ง ์Šฌ๋ž˜์‹œ ์ œ๊ฑฐ: /path/ โ†’ /path
{ source: "/:path*/", destination: "/:path*", permanent: true },

// B) www โ†’ non-www: https://www.<host>/* โ†’ https://<host>/*
{
source: "/:path*",
has: [{ type: "host", value: WWW_HOST }],
destination: `${DEST_ORIGIN}/:path*`,
permanent: true,
},
];
},
};

export default nextConfig;
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"postbuild": "next-sitemap",
"sitemap": "next-sitemap",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings=0",
"lint:fix": "pnpm lint --fix",
"format": "prettier --write .",
Expand Down Expand Up @@ -55,6 +57,7 @@
"eslint-plugin-prettier": "^5.5.4",
"husky": "^9.1.7",
"lint-staged": "^16.2.0",
"next-sitemap": "^4.2.3",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwindcss": "^4",
Expand Down
37 changes: 37 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# *
User-agent: *
Allow: /

# *
User-agent: *
Disallow: /api/
Disallow: /admin/
Disallow: /debug
Disallow: /lab/

# Host
Host: https://myplanmate.vercel.app

# Sitemaps
Sitemap: https://myplanmate.vercel.app/sitemap.xml
Sitemap: https://myplanmate.vercel.app/server-sitemap.xml
4 changes: 4 additions & 0 deletions public/sitemap-0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url><loc>https://myplanmate.vercel.app</loc><lastmod>2025-10-21T17:20:15.421Z</lastmod><changefreq>daily</changefreq><priority>1</priority><xhtml:link rel="alternate" hreflang="ko" href="https://myplanmate.vercel.app/ko"/><xhtml:link rel="alternate" hreflang="en" href="https://myplanmate.vercel.app/en"/></url>
</urlset>
5 changes: 5 additions & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://myplanmate.vercel.app/sitemap-0.xml</loc></sitemap>
<sitemap><loc>https://myplanmate.vercel.app/server-sitemap.xml</loc></sitemap>
</sitemapindex>
22 changes: 11 additions & 11 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
LOCALE,
OG_DEFAULT_IMAGE,
SITE_NAME,
SITE_URL,
SITE_URL, // โœ… constants์—์„œ SITE_URL ์‚ฌ์šฉ
TITLE_TEMPLATE,
} from "@/seo/constants";

export const metadata: Metadata = {
// ์ ˆ๋Œ€ URL ๊ธฐ์ค€์  (canonical/OG ์ ˆ๋Œ€๊ฒฝ๋กœ ๋ณ€ํ™˜์— ์‚ฌ์šฉ)
// โœ… ์ ˆ๋Œ€ URL ๊ธฐ์ค€์  (canonical / OG ์ ˆ๋Œ€๊ฒฝ๋กœ ๋ณ€ํ™˜์šฉ)
metadataBase: new URL(SITE_URL),

// ์ „์—ญ ํƒ€์ดํ‹€ ๊ทœ์น™
Expand Down Expand Up @@ -48,6 +48,15 @@ export const metadata: Metadata = {
twitter: {
card: "summary_large_image",
},

// canonical ๋ฐ ์–ธ์–ด๋ณ„ hreflang
alternates: {
canonical: "/", // => https://myplanmate.vercel.app/
languages: {
ko: "/ko", // => https://myplanmate.vercel.app/ko
en: "/en", // => https://myplanmate.vercel.app/en
},
Comment on lines +52 to +58

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid global canonical/hreflang pointing to only the home page

The new alternates block in the root layout sets canonical: "/" and fixed language URLs for /ko and /en. Because root layout metadata is inherited by every route unless explicitly overridden, all nonโ€‘home pages will now emit <link rel="canonical" href="https://myplanmate.vercel.app/"> and hreflang links to /ko and /en, regardless of the current path. This causes search engines to treat every page as a duplicate of the home page and drop the correct localized versions from the index unless each page defines its own metadata. Consider removing the global canonical/hreflang or generating them per route instead.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

},
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
Expand All @@ -60,15 +69,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
- ๋ชจ๋“  ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ผํ•œ client & cache ๊ณต์œ 
- useQuery, useMutation ํ›…์ด ์–ด๋””์„œ๋“  ์ •์ƒ ๋™์ž‘
*/}
{/*
โœ… ์ถ”ํ›„ ํ™•์žฅ ์˜ˆ์‹œ:
<AuthProvider>
<ThemeProvider>
<Providers>{children}</Providers>
</ThemeProvider>
</AuthProvider>
*/}

<Providers>{children}</Providers>
</body>
</html>
Expand Down