diff --git a/components/common/comment/CommentUserInfo.tsx b/components/common/comment/CommentUserInfo.tsx index ce76a5a8..bda37cfa 100644 --- a/components/common/comment/CommentUserInfo.tsx +++ b/components/common/comment/CommentUserInfo.tsx @@ -17,7 +17,9 @@ export default function CommentUserInfo({ className }: { className?: string }) {

) : ( // TODO: 추후 서버 수정되면 익명회원 ID를 보여주는 문구 추가 - <> +

+ 익명의 댑댑이님 의견을 남겨주세요! +

)} ); diff --git a/components/meta/MetaHead.tsx b/components/meta/MetaHead.tsx index fd160dad..df2f4c92 100644 --- a/components/meta/MetaHead.tsx +++ b/components/meta/MetaHead.tsx @@ -4,36 +4,59 @@ import 뎁구리_og from '@public/image/뎁구리/뎁구리_og.png'; import { META, SITE_URL } from '@/constants/metaData'; -const MetaHead = ({ - title, - description, - url, - keyword, - image, -}: { +export type MetaHeadProps = { title?: string; description?: string; url?: string; keyword?: string[]; image?: string; -}) => { +}; + +const toAbsoluteUrl = (src: string) => { + if (/^https?:\/\//.test(src)) { + return src; + } + + try { + return new URL(src, SITE_URL).toString(); + } catch { + return `${SITE_URL}${src.startsWith('/') ? src : `/${src}`}`; + } +}; + +const MetaHead = ({ title, description, url, keyword, image }: MetaHeadProps) => { + const metaTitle = title ?? META.MAIN.title; + const metaDescription = description ?? META.MAIN.description; + const metaKeywords = (keyword ?? META.MAIN.keyword).join(','); + const metaUrl = url ?? SITE_URL; + const defaultImage = toAbsoluteUrl(뎁구리_og.src); + const metaImage = image ? toAbsoluteUrl(image) : defaultImage; + return ( - {title || META.MAIN.title} - + {metaTitle} + - - + + + {/* Open Graph */} + - {/* TODO: 최종 url은 변경 필요 */} - - - - {/* 트위터용 */} - - - + + + + + + {/* Twitter */} + + + + + + + ); }; + export default MetaHead; diff --git a/pages/_app.page.tsx b/pages/_app.page.tsx index fc0dc771..23c4a3a1 100644 --- a/pages/_app.page.tsx +++ b/pages/_app.page.tsx @@ -8,14 +8,18 @@ import { QueryClient, QueryClientProvider, HydrationBoundary } from '@tanstack/r import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import Layout from '@components/common/layout'; +import MetaHead, { type MetaHeadProps } from '@components/meta/MetaHead'; import useSetAxiosConfig from '@/api/useSetAxiosConfig'; import { DAY, HALF_DAY } from '@/constants/TimeConstants'; +import { META } from '@/constants/metaData'; import { MediaQueryProvider } from '@/contexts/MediaQueryContext'; import '@/styles/globals.css'; import * as gtag from '../lib/gtag'; +type ComponentWithMeta = AppProps['Component'] & { meta?: MetaHeadProps }; + export default function MyApp({ Component, pageProps }: AppProps) { useSetAxiosConfig(); console.log('process.env.NODE_ENV', process.env.NODE_ENV); @@ -54,18 +58,31 @@ export default function MyApp({ Component, pageProps }: AppProps) { }; }, [router.events]); + const componentMeta = (Component as ComponentWithMeta)?.meta; + const meta: MetaHeadProps = + (pageProps.meta as MetaHeadProps | undefined) ?? componentMeta ?? META.MAIN; + return ( - - - - - - - - - - - - + <> + + + + + + + + + + + + + + ); } diff --git a/pages/main/index.page.tsx b/pages/main/index.page.tsx index 18e0b69e..0dc662c4 100644 --- a/pages/main/index.page.tsx +++ b/pages/main/index.page.tsx @@ -8,10 +8,10 @@ import DevGuriError from '@components/common/error/DevGuriError'; import ArrowWithTitle from '@components/common/title/ArrowWithTitle'; import MainTechBlogSection from '@components/features/main/MainTechBlogSection'; import MainCardComponent from '@components/features/main/mainCard/MainCardComponent'; -import MetaHead from '@components/meta/MetaHead'; import DevLogo from '@public/image/devdevdevLogo.svg'; +import { META } from '@/constants/metaData'; import { useMediaQueryContext } from '@/contexts/MediaQueryContext'; const DynamicPickComponent = dynamic( @@ -39,58 +39,63 @@ export default function Index() { }; return ( - <> - -
- +
+ -
-
- -
- - ( - - )} - > - - -
-
+
+
+ +
+ + ( + + )} + > + + +
+
-
- -
- - ( - - )} - > - - -
-
-
+
+ +
+ + ( + + )} + > + + +
+
- +
); } + +export function getStaticProps() { + return { + props: { + meta: META.MAIN, + }, + }; +} \ No newline at end of file diff --git a/pages/pickpickpick/[id]/index.page.tsx b/pages/pickpickpick/[id]/index.page.tsx index 95e0f51a..59e4725d 100644 --- a/pages/pickpickpick/[id]/index.page.tsx +++ b/pages/pickpickpick/[id]/index.page.tsx @@ -1,4 +1,5 @@ import Link from 'next/link'; +import type { NextPage } from 'next'; import { useRouter } from 'next/router'; import DevLoadingComponent from '@pages/loading/index.page'; @@ -16,7 +17,9 @@ import { PICK_VOTE_MODIFIED_MODAL, } from '@components/common/modals/modalConfig/pickVote'; import MoreButton from '@components/common/moreButton'; +import type { MetaHeadProps } from '@components/meta/MetaHead'; +import { META } from '@/constants/metaData'; import { ROUTES } from '@/constants/routes'; import { useMediaQueryContext } from '@/contexts/MediaQueryContext'; @@ -27,7 +30,9 @@ import SimilarPick from './components/SimilarPick'; import VoteCard from './components/VoteCard'; import usePickDetailHandlers from './handlers/usePickDetailHandlers'; -export default function Index() { +type NextPageWithMeta = NextPage & { meta?: MetaHeadProps }; + +const PickDetailPage: NextPageWithMeta = () => { const router = useRouter(); const { id } = router.query; @@ -148,4 +153,8 @@ export default function Index() {
); -} +}; + +PickDetailPage.meta = META.PICK; + +export default PickDetailPage; diff --git a/pages/pickpickpick/index.page.tsx b/pages/pickpickpick/index.page.tsx index b9d59de7..7ffc280d 100644 --- a/pages/pickpickpick/index.page.tsx +++ b/pages/pickpickpick/index.page.tsx @@ -17,7 +17,6 @@ import { Dropdown } from '@components/common/dropdowns/dropdown'; import MobileDropdown from '@components/common/dropdowns/mobileDropdown'; import { LoginModal } from '@components/common/modals/modal'; import { MobilePickSkeletonList, PickSkeletonList } from '@components/common/skeleton/pickSkeleton'; -import MetaHead from '@components/meta/MetaHead'; import IconPencil from '@public/image/pencil-alt.svg'; @@ -42,8 +41,6 @@ export default function Index() { const { isMobile } = useMediaQueryContext(); - const { title, description, keyword, url } = META.PICK; - const { pickData, isFetchingNextPage, hasNextPage, status, onIntersect } = useInfinitePickData( sortOption as PickDropdownProps, ); @@ -105,63 +102,68 @@ export default function Index() { }; return ( - <> - -
-
-

- 픽픽픽 💘 -

- - {!isMobile && ( -
- - - {loginStatus === 'login' ? ( - - } - type='button' - /> - - ) : ( +
+
+

+ 픽픽픽 💘 +

+ + {!isMobile && ( +
+ + + {loginStatus === 'login' ? ( + } - onClick={() => { - openLoginModal(); - setDescription('댑댑이가 되면 픽픽픽을 작성할 수 있어요 🥳'); - }} type='button' /> - )} -
- )} -
- {getStatusComponent()} -
- {isMobile && - (loginStatus === 'login' ? ( - - - - ) : ( - { - openLoginModal(); - setDescription('댑댑이가 되면 픽픽픽을 작성할 수 있어요 🥳'); - }} - /> - ))} - {isLoginModalOpen && loginStatus !== 'login' && } + + ) : ( + } + onClick={() => { + openLoginModal(); + setDescription('댑댑이가 되면 픽픽픽을 작성할 수 있어요 🥳'); + }} + type='button' + /> + )} +
+ )}
- + {getStatusComponent()} +
+ {isMobile && + (loginStatus === 'login' ? ( + + + + ) : ( + { + openLoginModal(); + setDescription('댑댑이가 되면 픽픽픽을 작성할 수 있어요 🥳'); + }} + /> + ))} + {isLoginModalOpen && loginStatus !== 'login' && } +
); } + +export function getStaticProps() { + return { + props: { + meta: META.PICK, + }, + }; +} \ No newline at end of file diff --git a/pages/techblog/[id]/index.page.tsx b/pages/techblog/[id]/index.page.tsx index b367cd6b..cbb22eb3 100644 --- a/pages/techblog/[id]/index.page.tsx +++ b/pages/techblog/[id]/index.page.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import type { NextPage } from 'next'; import { useRouter } from 'next/router'; import DevLoadingComponent from '@pages/loading/index.page'; @@ -13,7 +14,9 @@ import WritableComment from '@components/common/comment/WritableComment'; import DevGuriHorizontalError from '@components/common/error/DevGuriHorizontalError'; import MobileToListButton from '@components/common/mobile/mobileToListButton'; import { LoginModal } from '@components/common/modals/modal'; +import type { MetaHeadProps } from '@components/meta/MetaHead'; +import { META } from '@/constants/metaData'; import { ROUTES } from '@/constants/routes'; import { useMediaQueryContext } from '@/contexts/MediaQueryContext'; @@ -24,7 +27,9 @@ import CompanyInfoCard from '../components/CompanyInfoCard'; import TechDetailCard from '../components/TechDetailCard'; import { TechCardProps } from '../types/techBlogType'; -export default function Page() { +type NextPageWithMeta = NextPage & { meta?: MetaHeadProps }; + +const TechBlogDetailPage: NextPageWithMeta = () => { const router = useRouter(); const techArticleId = router.query.id as string | undefined; @@ -116,4 +121,8 @@ export default function Page() { {isLoginModalOpen && loginStatus !== 'login' && } ); -} +}; + +TechBlogDetailPage.meta = META.TECH; + +export default TechBlogDetailPage; diff --git a/pages/techblog/index.page.tsx b/pages/techblog/index.page.tsx index 8acc7712..05e00dcc 100644 --- a/pages/techblog/index.page.tsx +++ b/pages/techblog/index.page.tsx @@ -21,7 +21,6 @@ import { TechSkeletonList, } from '@components/common/skeleton/techBlogSkeleton'; import SearchInput from '@components/common/techSearchInput/searchInput'; -import MetaHead from '@components/meta/MetaHead'; import { INITIAL_TECH_SORT_OPTION, techBlogDropdownOptions } from '@/constants/DropdownOption'; import { ONE_DAY_IN_SECONDS } from '@/constants/TimeConstants'; @@ -62,8 +61,6 @@ export default function Index() { const { companyId, resetCompanyInfo } = useCompanyInfoStore(); const { setToastInvisible } = useToastVisibleStore(); - const { title, description, keyword, url } = META.TECH; - const { techBlogData, isFetchingNextPage, hasNextPage, status, onIntersect } = useInfiniteTechBlogData(sortOption as TechBlogDropdownProps, searchKeyword, companyId); @@ -128,40 +125,37 @@ export default function Index() { }; return ( - <> - -
-
-
-

- 기술블로그 🧪 -

- -
-
- {/* 구독영역 */} - ( - - )} - > - - - {/* 총갯수 & 드롭다운 영역 */} -
-

- 총 {totalArticleCnt}건 -

- {isMobile ? : } +
+
+
+

+ 기술블로그 🧪 +

+
- {/* 게시글 목록 */} - {getStatusComponent(techBlogData, status)} -
- + {/* 구독영역 */} + ( + + )} + > + + + {/* 총갯수 & 드롭다운 영역 */} +
+

+ 총 {totalArticleCnt}건 +

+ {isMobile ? : } +
+ {/* 게시글 목록 */} + {getStatusComponent(techBlogData, status)} +
+
); } @@ -201,6 +195,7 @@ export async function getStaticProps() { return { props: { dehydratedState: dehydrate(queryClient), + meta: META.TECH, }, revalidate: ONE_DAY_IN_SECONDS, // 페이지를 하루(24시간)마다 다시 생성 }; @@ -208,4 +203,4 @@ export async function getStaticProps() { console.error('Error prefetching tech blog data:', error); throw new Error('데이터를 프리패치 하는중 오류가 발생했습니다.'); } -} +} \ No newline at end of file