Skip to content

Commit 991f825

Browse files
committed
Refactor article metadata structure to use slugs instead of URLs, implement multilingual support, and enhance SEO metadata generation
- Updated article metadata files to replace `url` with `slug` for better routing and localization. - Introduced language switching functionality in article headers. - Added support for multiple languages in article metadata and sitemap generation. - Created new components and utilities for handling localization and language detection. - Implemented middleware for locale prefixing in routes. - Enhanced category labels to support localization. - Added tests for portfolio page metadata. - Created robots.txt and sitemap.xml for improved SEO.
1 parent e8a3d8b commit 991f825

File tree

87 files changed

+657
-215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+657
-215
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ NEXT_PUBLIC_GTM_ID=G-xxxxxx
66
NEXT_PUBLIC_UTTERANCES_REPO=owner/repo
77

88
# base url
9-
BASE_URL=
9+
BASE_URL=http://localhost:3000
1010
NEXT_PUBLIC_BASE_URL=

AGENTS.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This project is a Next.js 15 + TypeScript blog/site. Use the conventions below t
1313
## Build, Test, and Development Commands
1414
- `pnpm i`: Install dependencies.
1515
- `pnpm dev`: Run Next dev server.
16-
- `pnpm build`: Production build + generate sitemap (`next-sitemap`).
16+
- `pnpm build`: Production build. Sitemap/robots via Next routes.
1717
- `pnpm start`: Start the built app.
1818
- `pnpm test` / `pnpm test:ci`: Run Vitest (happy‑dom env).
1919
- `pnpm lint` / `pnpm type-check`: Lint with ESLint and check types with `tsc`.
@@ -38,4 +38,3 @@ This project is a Next.js 15 + TypeScript blog/site. Use the conventions below t
3838
## Security & Configuration
3939
- Copy `.env.example``.env` (managed with `direnv`). Never commit secrets.
4040
- Client‑exposed values must use `NEXT_PUBLIC_*` (e.g., `NEXT_PUBLIC_SITE_URL=https://example.com`).
41-

next-seo.config.js

Lines changed: 0 additions & 28 deletions
This file was deleted.

next-sitemap.config.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"test": "vitest",
88
"test:ci": "vitest run",
99
"dev": "next dev",
10-
"build": "next build && next-sitemap --config next-sitemap.config.js",
10+
"build": "next build",
1111
"start": "next start",
1212
"analyze": "ANALYZE=true pnpm build",
1313
"lint": "eslint .",
@@ -61,7 +61,6 @@
6161
"eslint-plugin-react-hooks": "^5.2.0",
6262
"happy-dom": "^18.0.1",
6363
"knip": "^5.61.3",
64-
"next-sitemap": "^4.2.3",
6564
"postcss": "^8.5.6",
6665
"tailwindcss": "^4.1.11",
6766
"typescript": "^5.8.3",

pnpm-lock.yaml

Lines changed: 0 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/ListItem.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
"use client";
22

33
import type { ArticleItem } from "@modules/article/types";
4-
import { metadataBase } from "@modules/metadata/constants";
4+
import { getCategoryLabel } from "@modules/category";
55
import dayjs from "dayjs";
66
import { motion } from "motion/react";
77
import Link from "next/link";
8+
import { usePathname } from "next/navigation";
89

910
const listVariants = {
1011
hidden: { opacity: 0 },
1112
visible: {
1213
opacity: 1,
13-
// transition: {
14-
// staggerChildren: 0.03,
15-
// },
1614
},
1715
};
1816

@@ -35,10 +33,20 @@ export function List({ children }: { children: React.ReactNode }) {
3533
);
3634
}
3735

38-
export function ListItem({ item }: { item: ArticleItem }) {
39-
const { createdAt, summary, title, url, category } = item;
36+
export function ListItem({
37+
item,
38+
basePathname,
39+
}: {
40+
item: ArticleItem;
41+
basePathname: string;
42+
}) {
43+
const { createdAt, summary, title, slug, category } = item;
4044

41-
const href = new URL(url, metadataBase).pathname;
45+
const href = new URL(slug, basePathname).pathname;
46+
const pathname = usePathname();
47+
const firstSeg = (pathname.split("/")[1] ?? "") as "ko" | "en" | "";
48+
const locale: "ko" | "en" =
49+
firstSeg === "ko" || firstSeg === "en" ? firstSeg : "ko";
4250

4351
return (
4452
<motion.li
@@ -55,7 +63,7 @@ export function ListItem({ item }: { item: ArticleItem }) {
5563
{category && (
5664
<>
5765
<span className="opacity-50"> | </span>
58-
<span>{category}</span>
66+
<span>{getCategoryLabel(category, locale)}</span>
5967
</>
6068
)}
6169
</div>

src/app/article/2021-12/node-js-stream/metadata.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import dayjs from "dayjs";
44
import i1 from "./i.png";
55

66
export const title = "Node.js 스트림(stream) 개념을 익혀보자";
7-
export const url = "https://springfall.cc/article/2021-12/node-js-stream";
7+
const slug = "article/2021-12/node-js-stream";
88
export const summary =
99
"Stream 자료구조를 간단하게 익히고 Readable 클래스를 직접 확장하여 구현해봅니다";
1010
export const createdAt = dayjs("2021-12-07").toISOString();
@@ -20,7 +20,7 @@ export const item: ArticleItem = {
2020
imageAlt,
2121
summary,
2222
title,
23-
url,
23+
slug,
2424
category,
2525
tags: [],
2626
};

src/app/article/2022-09/company/metadata.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import dayjs from "dayjs";
33
import i4 from "./i4.jpeg";
44

55
export const title = "개발자로서 회사에 들어와서 만 1년이 되기 직전 느낀 점";
6-
export const url = "https://springfall.cc/article/2022-09/company";
6+
const slug = "article/2022-09/company";
77
export const summary =
88
"기록을 남겨두지 않으면 현재의 모습이 어디로부터 기원했는지를 알 방도가 없어집니다.";
99
export const createdAt = dayjs("2022-09-01").toISOString();
@@ -18,6 +18,6 @@ export const item: ArticleItem = {
1818
imageAlt,
1919
summary,
2020
title,
21-
url,
21+
slug,
2222
tags: [],
2323
};

src/app/article/2022-10/hello-sadness/metadata.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import dayjs from "dayjs";
44
import i1 from "./i1.jpeg";
55

66
export const title = "[리뷰] 슬픔이여 안녕";
7-
export const url = "https://springfall.cc/article/2022-10/hello-sadness";
7+
const slug = "article/2022-10/hello-sadness";
88
export const summary =
99
"세상엔 다양한 세계가 있고, 화해할 수도 있지만 양립하지 못할 수도 있다. 그런 사실에 직면하는 것이 슬픔인 것 같다.";
1010
export const createdAt = dayjs("2022-10-02").toISOString();
@@ -20,7 +20,7 @@ export const item: ArticleItem = {
2020
imageAlt,
2121
summary,
2222
title,
23-
url,
23+
slug,
2424
category,
2525
tags: [],
2626
};

0 commit comments

Comments
 (0)