diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5e86f360..9b480225 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,7 @@ import { ReactNode } from 'react'; import type { Metadata, Viewport } from 'next'; +import { ReferrerEnum } from 'next/dist/lib/metadata/types/metadata-types'; +import { OpenGraphType } from 'next/dist/lib/metadata/types/opengraph-types'; import { ToastContainer } from 'react-toastify'; import { GoogleTagManager } from '@next/third-parties/google'; @@ -9,6 +11,7 @@ import '@/styles/GlobalStyles.css'; import * as styles from './layout.css'; import CommonProvider from './_context/CommonProvider'; +import METADATA from '@/lib/constants/metadata'; export const viewport: Viewport = { width: 'device-width', @@ -19,27 +22,24 @@ export const viewport: Viewport = { }; export const metadata: Metadata = { - // Template Object title: { - template: '%s | ListyWave', - default: 'ListyWave', // 대체 제목 (required), + template: METADATA.title.template, + default: METADATA.title.default, }, - description: - "What’s In Your List? 🌊 나의 취향을 리스트로 기록하고, 공유하고, 발견해요. 리스티웨이브에서 모든 기준은 '나의 취향'이에요. 내 취향 가득한 편안한 공간이 되면 좋겠습니다.", - authors: [{ name: '에잇🩷' }], - generator: 'Next.js', - applicationName: 'ListyWave', - referrer: 'origin-when-cross-origin', // Referrer-Policy - keywords: ['ListyWave', 'list', 'SNS'], - metadataBase: new URL('https://listywave.com'), + description: METADATA.description.default, + authors: [{ name: METADATA.authors.name, url: METADATA.authors.url }], + generator: METADATA.generator, + applicationName: METADATA.applicationName, + referrer: METADATA.referrer as ReferrerEnum, + keywords: METADATA.keywords, + metadataBase: new URL(METADATA.url), openGraph: { - title: 'ListyWave', - description: - "What’s In Your List? 🌊 나의 취향을 리스트로 기록하고, 공유하고, 발견해요. 리스티웨이브에서 모든 기준은 '나의 취향'이에요. 내 취향 가득한 편안한 공간이 되면 좋겠습니다.", - url: 'https://listywave.com', - type: 'website', - siteName: 'ListyWave', - locale: 'ko', + title: METADATA.title.openGraph, + description: METADATA.description.default, + url: METADATA.url, + type: METADATA.type as OpenGraphType, + siteName: METADATA.siteName, + locale: METADATA.locale, }, }; diff --git a/src/app/list/[listId]/page.tsx b/src/app/list/[listId]/page.tsx index cb8f4bc1..e74f0fbf 100644 --- a/src/app/list/[listId]/page.tsx +++ b/src/app/list/[listId]/page.tsx @@ -1,6 +1,37 @@ +import { Metadata, ResolvingMetadata } from 'next'; import * as styles from './ListDetail.css'; import ListInformation from '@/app/list/[listId]/_components/ListDetailOuter/ListInformation'; +import axiosInstance from '@/lib/axios/axiosInstance'; +import { ListDetailType } from '@/lib/types/listType'; +import METADATA from '@/lib/constants/metadata'; + +interface ListDetailProps { + params: { listId: number }; +} + +export async function generateMetadata({ params }: ListDetailProps, parent: ResolvingMetadata): Promise { + const listId = params.listId; + const { data } = await axiosInstance.get(`/lists/${listId}`); + const { title, ownerNickname, collaborators, description, items } = data; + + const previousImages = (await parent).openGraph?.images || []; + const listType = collaborators.length === 0 ? 'Mylist' : 'Collabo-list'; + + return { + title: { + absolute: `${ownerNickname}'s ${listType} - ${data.title}`, + }, + description: `${description}`, + authors: [{ name: `${ownerNickname}` }], + openGraph: { + title: `${title} By.${ownerNickname}`, + description: `${description || `${ownerNickname}님의 취향을 기록한 리스트입니다.`}`, + url: `${METADATA.url}/list/${listId}`, + images: [`${items[0].imageUrl}`, ...previousImages], + }, + }; +} export default function ListDetailPage() { return ( diff --git a/src/app/robots.ts b/src/app/robots.ts new file mode 100644 index 00000000..081320c1 --- /dev/null +++ b/src/app/robots.ts @@ -0,0 +1,12 @@ +import DOMAIN_URL from '@/lib/constants/domain'; +import type { MetadataRoute } from 'next'; + +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: '*', + allow: '/', + }, + sitemap: `${DOMAIN_URL}/sitemap.xml`, + }; +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts new file mode 100644 index 00000000..30e9df2f --- /dev/null +++ b/src/app/sitemap.ts @@ -0,0 +1,25 @@ +import type { MetadataRoute } from 'next'; +import DOMAIN_URL from '@/lib/constants/domain'; + +export default async function sitemap(): Promise { + return [ + { + url: DOMAIN_URL, + lastModified: new Date(), + changeFrequency: 'weekly', + priority: 1, + }, + { + url: `${DOMAIN_URL}/intro`, + lastModified: new Date(), + changeFrequency: 'yearly', + priority: 0.8, + }, + { + url: `${DOMAIN_URL}/search`, + lastModified: new Date(), + changeFrequency: 'weekly', + priority: 0.8, + }, + ]; +} diff --git a/src/app/user/[userId]/collabolist/page.tsx b/src/app/user/[userId]/collabolist/page.tsx index 9d109b4b..3f562446 100644 --- a/src/app/user/[userId]/collabolist/page.tsx +++ b/src/app/user/[userId]/collabolist/page.tsx @@ -1,4 +1,4 @@ -import { Metadata } from 'next'; +import { Metadata, ResolvingMetadata } from 'next'; import Profile from '../_components/Profile'; import Content from '../_components/Content'; @@ -6,16 +6,33 @@ import FloatingContainer from '@/components/floatingButton/FloatingContainer'; import PlusOptionFloatingButton from '@/components/floatingButton/PlusOptionFloatingButton'; import ArrowUpFloatingButton from '@/components/floatingButton/ArrowUpFloatingButton'; +import axiosInstance from '@/lib/axios/axiosInstance'; +import { UserType } from '@/lib/types/userProfileType'; +import METADATA from '@/lib/constants/metadata'; + interface CollaboListPageProps { - params: { - userId: number; - }; + params: { userId: number }; } -export const metadata: Metadata = { - title: 'Collabo List', - description: '콜라보레이터와 함께 기록한 리스트 입니다.', -}; +export async function generateMetadata({ params }: CollaboListPageProps, parent: ResolvingMetadata): Promise { + const userId = params.userId; + const { data } = await axiosInstance.get(`/users/${userId}`); + + const previousImages = (await parent).openGraph?.images || []; + + return { + title: { + absolute: `${data.nickname}'s Collabo-list`, + }, + authors: [{ name: `${data.nickname}` }], + description: METADATA.description.collabolist, + openGraph: { + description: `${data.description || METADATA.description.collabolist}`, + url: `${METADATA.url}/user/${userId}/collabolist`, + images: [`${data.profileImageUrl}`, ...previousImages], + }, + }; +} export default function CollaboListPage({ params }: CollaboListPageProps) { return ( diff --git a/src/app/user/[userId]/mylist/page.tsx b/src/app/user/[userId]/mylist/page.tsx index 17d56917..289041f8 100644 --- a/src/app/user/[userId]/mylist/page.tsx +++ b/src/app/user/[userId]/mylist/page.tsx @@ -1,4 +1,4 @@ -import { Metadata } from 'next'; +import { Metadata, ResolvingMetadata } from 'next'; import Profile from '../_components/Profile'; import Content from '../_components/Content'; @@ -6,16 +6,33 @@ import FloatingContainer from '@/components/floatingButton/FloatingContainer'; import PlusOptionFloatingButton from '@/components/floatingButton/PlusOptionFloatingButton'; import ArrowUpFloatingButton from '@/components/floatingButton/ArrowUpFloatingButton'; +import axiosInstance from '@/lib/axios/axiosInstance'; +import { UserType } from '@/lib/types/userProfileType'; +import METADATA from '@/lib/constants/metadata'; + interface MyListPageProps { - params: { - userId: number; - }; + params: { userId: number }; } -export const metadata: Metadata = { - title: 'My List', - description: '나의 취향을 기록한 나만의 리스트 입니다.', -}; +export async function generateMetadata({ params }: MyListPageProps, parent: ResolvingMetadata): Promise { + const userId = params.userId; + const { data } = await axiosInstance.get(`/users/${userId}`); + + const previousImages = (await parent).openGraph?.images || []; + + return { + title: { + absolute: `${data.nickname}'s Mylist`, + }, + authors: [{ name: `${data.nickname}` }], + description: METADATA.description.mylist, + openGraph: { + description: `${data.description || METADATA.description.mylist}`, + url: `${METADATA.url}/user/${userId}/mylist`, + images: [`${data.profileImageUrl}`, ...previousImages], + }, + }; +} export default function MyListPage({ params }: MyListPageProps) { return ( diff --git a/src/lib/constants/domain.ts b/src/lib/constants/domain.ts new file mode 100644 index 00000000..8bf61cab --- /dev/null +++ b/src/lib/constants/domain.ts @@ -0,0 +1,3 @@ +const DOMAIN_URL = 'https://listywave.com'; + +export default DOMAIN_URL; diff --git a/src/lib/constants/metadata.ts b/src/lib/constants/metadata.ts new file mode 100644 index 00000000..ec77ca5a --- /dev/null +++ b/src/lib/constants/metadata.ts @@ -0,0 +1,29 @@ +import DOMAIN_URL from './domain'; + +const METADATA = { + title: { + template: '%s | ListyWave', // Template Object + default: 'ListyWave | 리스티웨이브', + openGraph: 'ListyWave', + }, + description: { + default: + "나의 취향을 리스트로 기록하고, 공유하고, 발견해요. 리스티웨이브에서 모든 기준은 '나의 취향'이에요. 내 취향 가득한 편안한 공간이 되면 좋겠습니다.", + mylist: '나의 취향을 기록한 리스트 입니다.', + collabolist: '콜라보레이터와 함께 기록한 콜라보 리스트 입니다.', + }, + authors: { + name: '에잇🩷', + url: 'https://github.com/8-Sprinters', + }, + generator: 'Next.js', + applicationName: 'ListyWave', + referrer: 'origin-when-cross-origin', // Referrer-Policy + keywords: ['ListyWave', 'list', '리스티웨이브'], + url: DOMAIN_URL, + type: 'website', + siteName: 'ListyWave', + locale: 'ko', +}; + +export default METADATA;