Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setting : SEO 를 위한 세팅 #215

Merged
merged 8 commits into from
Oct 18, 2024
Merged
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
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEXT_PUBLIC_API_BASE_URL=https://fe-adv-project-together-dallaem.vercel.app/3-4
NEXT_PUBLIC_BASE_URL=https://soothe-with-me.vercel.app
6 changes: 2 additions & 4 deletions src/app/(auth)/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import SignInForm from '@/app/(auth)/signin/_component/SignInForm';
import { Metadata } from 'next';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = {
title: '로그인 | Soothe With Me',
description: 'Soothe With Me 로그인 페이지입니다.',
};
export const metadata: Metadata = pageMetadata('로그인', '/signin');

const SignIn = () => {
return <SignInForm />;
Expand Down
6 changes: 2 additions & 4 deletions src/app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import SignUpForm from '@/app/(auth)/signup/_component/SignUpForm';
import { Metadata } from 'next';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = {
title: '회원가입 | Soothe With Me',
description: 'Soothe With Me 회원가입 페이지입니다.',
};
export const metadata: Metadata = pageMetadata('회원가입', '/signup');

const SignUp = () => {
return <SignUpForm />;
Expand Down
11 changes: 11 additions & 0 deletions src/app/(main)/gatherings/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ import getGatheringParticipants from '@/app/api/actions/gatherings/getGatheringP
import getGatheringInfo from '@/app/api/actions/gatherings/getGatheringInfo';
import { getUserData } from '@/app/api/actions/mypage/getUserData';
import { UserData } from '@/types/client.type';
import { gatheringMetadata } from '@/utils/makeMetadata';

export const generateMetadata = async ({
params,
}: {
params: {
id: number;
};
}) => {
return await gatheringMetadata(params.id);
};
Comment on lines +12 to +22
Copy link
Contributor Author

Choose a reason for hiding this comment

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

generateMetadata 는 동적으로 메타데이터를 생성하는 함수로,
동적라우팅 시 파라미터 값을 받아 메타데이터 생성에 적용할 수 있습니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

P4) (그냥 질문) 혹시 dynamic routes 가 적용된 페이지는 이런 식으로 페이지 내부에 직접 함수를 적용해줘야 하는 걸까요??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

dynamic routes 역시 기본적으로 layout 이나 page 파일에서 정적 메타데이터로 내보내기 할 수 있습니다.
다만 dynamic routes 의 세그먼트나 다른 파라미터 등을 이용해서 메타데이터를 동적으로 생성하고 싶을 때 generateMetadata 함수를 사용합니다. (dynamic routes가 아니어도 사용 가능)
즉 정적 생성, 동적 생성은 모든 라우트 종류에 가능하며 필수 요소가 아닙니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

오..... 상세한 답변 감사합니다!


const GatheringsDetailPage = async ({
params,
Expand Down
4 changes: 4 additions & 0 deletions src/app/(main)/gatherings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Metadata } from 'next';
import Header from './_component/Header';
import ClientSideGatherings from './_component/ClientSideGatherings';
import getGatherings from '@/app/api/actions/gatherings/getGatherings';
import { getUserData } from '@/app/api/actions/mypage/getUserData';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = pageMetadata('모임찾기', '/gatherings');

const GatheringsPage = async () => {
const gatherings = await getGatherings({
Expand Down
11 changes: 9 additions & 2 deletions src/app/(main)/mypage/created/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { getUserData } from '@/app/api/actions/mypage/getUserData';
import { Metadata } from 'next';
import { redirect } from 'next/navigation';
import ClientSideGatherings from './_component/ClientSideGatherings';
import { getUserData } from '@/app/api/actions/mypage/getUserData';
import getGatherings from '@/app/api/actions/gatherings/getGatherings';
import { redirect } from 'next/navigation';
import { SORT_OPTIONS_MAP } from '@/constants/common';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = pageMetadata(
'내가 만든 모임',
'/mypage/created',
);

const CreatedPage = async () => {
const userData = await getUserData();
Expand Down
4 changes: 4 additions & 0 deletions src/app/(main)/mypage/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Metadata } from 'next';
import UserProfileLayout from '@/app/components/UserProfileLayout/UserProfileLayout';
import { ReactNode } from 'react';
import Tab from './_component/Tab';
import { getUserData } from '@/app/api/actions/mypage/getUserData';
import { redirect } from 'next/navigation';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = pageMetadata('나의 리뷰', '/mypage/review');

const Layout = async ({
children,
Expand Down
6 changes: 2 additions & 4 deletions src/app/(main)/mypage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { Metadata } from 'next';
import MyGatheringList from './_component/MyGatheringList';
import getMyGatherings from '@/app/api/actions/gatherings/getMyGatherings';
import { getUserData } from '@/app/api/actions/mypage/getUserData';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = {
title: '나의 모임 | Soothe With Me',
description: 'Soothe With Me 나의 모임 페이지입니다.',
};
export const metadata: Metadata = pageMetadata('나의 모임', '/mypage');

const Mygatherings = async () => {
const myGatherings = await getMyGatherings();
Expand Down
4 changes: 4 additions & 0 deletions src/app/(main)/reviews/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Metadata } from 'next';
import Header from './_components/Hearder';
import ClientSideReviews from './_components/ClientSideReviews';
import getReviewList from '@/app/api/actions/reviews/getReviewList';
import getReviewScore from '@/app/api/actions/reviews/getReviewScore';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = pageMetadata('모든 리뷰', '/reviews');

const ReviewsPage = async () => {
const reviewList = await getReviewList({ type: 'DALLAEMFIT' });
Expand Down
1 change: 1 addition & 0 deletions src/app/(main)/saved/_component/SavedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const SavedList = ({ dataList }: SavedListProps) => {
{dataList.map((item, index) => (
<div
key={item.id}
className='mb-24'
ref={
index === 0
? firstGatheringRef
Expand Down
15 changes: 15 additions & 0 deletions src/app/(main)/saved/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Metadata } from 'next';
import { ReactNode } from 'react';
import { pageMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = pageMetadata('찜한 모임', '/saved');

const Layout = ({
children,
}: Readonly<{
children: ReactNode;
}>) => {
return <>{children}</>;
};

export default Layout;
4 changes: 2 additions & 2 deletions src/app/(main)/saved/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use client';

import { Metadata } from 'next';
import Tabs from '@/app/components/Tabs/Tabs';
import Chips from '@/app/components/Chips/Chips';
import Filters from '@/app/components/Filters/Filters';
import MotionWrapper from '@/app/components/MotionWrapper/MotionWrapper';
import Header from './_component/Header';
import SavedList from './_component/SavedList';
import useSavedGatherings from '@/hooks/useSavedGatherings';
import { useSavedGatheringList } from '@/context/SavedGatheringContext';
import { SORT_OPTIONS } from '@/constants/common';

import MotionWrapper from '@/app/components/MotionWrapper/MotionWrapper';

const SavedPage = () => {
const { savedGatherings } = useSavedGatheringList();

Expand Down
1 change: 0 additions & 1 deletion src/app/components/CardList/CardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import Tag from '@/app/components/Tag/Tag';
import InfoChip from '@/app/components/Chip/InfoChip';
import ProgressBar from '@/app/components/ProgressBar/ProgressBar';

// TODO : optional props를 필수로 변경
interface CardProps {
data: GatheringType;
isSaved?: boolean;
Expand Down
11 changes: 3 additions & 8 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ import { toastOptions } from '@/constants/toast.config';
import { Toaster } from 'react-hot-toast';
import { getCookie } from './api/actions/cookie/cookie';

export const metadata: Metadata = {
title: 'Soothe With Me',
description:
'유저가 바쁜 일상 속 휴식을 위한 다양한 모임을 탐색하고 참여하며, 직접 모임을 개설하고 리뷰를 생성할 수 있는 서비스입니다.',
icons: {
icon: '/icons/bye.svg',
},
};
import { rootMetadata } from '@/utils/makeMetadata';

export const metadata: Metadata = rootMetadata;

export default async function RootLayout({
children,
Expand Down
5 changes: 5 additions & 0 deletions src/app/robots.txt
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
User-agent: *
Disallow: /mypage/
Allow: /

Sitemap: https://soothe-with-me.vercel.app/sitemap
68 changes: 68 additions & 0 deletions src/app/sitemap.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

최고에요👍👍

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { MetadataRoute } from 'next';

const BASE_URL = `${process.env.NEXT_PUBLIC_BASE_URL}`;

export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: BASE_URL,
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 0.5,
},
{
url: `${BASE_URL}/gatherings`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
{
url: `${BASE_URL}/saved`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.8,
},
{
url: `${BASE_URL}/reviews`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.8,
},
{
url: `${BASE_URL}/signup`,
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 0.5,
},
{
url: `${BASE_URL}/signin`,
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 0.5,
},
{
url: `${BASE_URL}/mypage`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.7,
},
{
url: `${BASE_URL}/mypage/review/writable`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.7,
},
{
url: `${BASE_URL}/mypage/review/written`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.7,
},
{
url: `${BASE_URL}/mypage/created`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.7,
},
];
}
124 changes: 124 additions & 0 deletions src/utils/makeMetadata.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

이건 진짜 손민수할 코드네요

Copy link
Contributor

Choose a reason for hiding this comment

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

메타데이터를 이렇게 관리할 수 있다는 걸 처음 알았네요...!

많이 배워갑니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const BASE_URL = `${process.env.NEXT_PUBLIC_BASE_URL}`;

export const rootMetadata = {
metadataBase: new URL(BASE_URL),
title: {
template: '%s | Soothe With Me',
default: 'Soothe With Me',
},
description:
'바쁜 일상 속 휴식을 위한 다양한 모임을 탐색하고 참여하며, 직접 모임을 개설하고 리뷰를 생성할 수 있는 서비스입니다.',
icons: {
icon: '/icons/bye.svg',
},
keywords:
'같이달램, Soothe with me, 달램핏, 워케이션, 마인드풀니스, 오피스스트레칭, 모임, 휴식, 소통, 힐링, 취미, 취향, 소모임',
robots: {
index: true,
follow: true,
},
openGraph: {
title: {
template: '%s | Soothe With Me',
default: 'Soothe With Me',
},
description:
'다양한 휴식 모임을 탐색하고 참여하며 직접 개설할 수 있는 서비스.',
url: BASE_URL,
siteName: 'Soothe With Me',
type: 'website',
locale: 'ko_KR',
images: [
{
url: '/images/login-img.svg',
width: 800,
height: 600,
alt: 'Soothe With Me',
},
],
},
twitter: {
title: {
template: '%s | Soothe With Me',
default: 'Soothe With Me',
},
description:
'바쁜 일상 속 휴식을 위한 다양한 모임을 제공하는 Soothe With Me 서비스.',
images: [
{
url: '/images/login-img.svg',
width: 800,
height: 600,
alt: 'Soothe With Me',
},
],
},
alternates: {
canonical: `${process.env.NEXT_PUBLIC_BASE_URL}/gatherings`,
},
other: {
'google-site-verification': 'EnQi5HcxKKMfPQq_KgrRrnkbL7g2qiHLz6KUXcBtNwo',
},
};

import getGatheringInfo from '@/app/api/actions/gatherings/getGatheringInfo';
import { GatheringInfoType } from '@/types/data.type';

export const pageMetadata = (page: string, path: string) => {
return {
title: {
template: '%s | Soothe With Me',
default: page,
},
description: `Soothe With Me ${page} 페이지입니다.`,
openGraph: {
title: page,
description: `Soothe With Me | ${page} .`,
url: `${BASE_URL}${path}`,
},
twitter: {
title: page,
description: `Soothe With Me | ${page} .`,
url: `${BASE_URL}${path}`,
},
};
};

export const gatheringMetadata = async (id: number) => {
const gatheringInfo: GatheringInfoType = await getGatheringInfo(id);

const name = gatheringInfo.name || '';
const type = gatheringInfo.type;
const img = gatheringInfo.image;

return {
title: name,
description: `${type} : ${name}`,
openGraph: {
title: name,
description: `${type} : ${name}`,
url: `${BASE_URL}/gatherings/${id}`,
images: [
{
url: img || '/images/login-img.svg',
width: 800,
height: 600,
alt: `${name} 모임 이미지`,
},
],
},
twitter: {
title: name,
description: `${type} : ${name}`,
url: `${BASE_URL}/gatherings/${id}`,
images: [
{
url: img || '/images/login-img.svg',
width: 800,
height: 600,
alt: `${name} 모임 이미지`,
},
],
},
};
};
Comment on lines +87 to +124
Copy link
Contributor

Choose a reason for hiding this comment

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

응용 미쳤네요!

Loading