Skip to content
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
56 changes: 56 additions & 0 deletions src/components/seo/Seo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Head from 'next/head';
import { useRouter } from 'next/router';
import {
DEFAULT_DESCRIPTION,
DEFAULT_OG_IMAGE,
DEFAULT_TITLE,
SITE_NAME,
TWITTER_CARD_TYPE,
buildAbsoluteUrl,
} from 'static/seo';

interface SeoProps {
title?: string;
description?: string;
url?: string;
image?: string;
type?: 'website' | 'article';
noindex?: boolean;
imageAlt?: string;
}

export default function Seo({
title,
description = DEFAULT_DESCRIPTION,
url,
image = DEFAULT_OG_IMAGE,
type = 'website',
noindex = false,
imageAlt,
}: SeoProps) {
const router = useRouter();
const resolvedTitle = title || DEFAULT_TITLE;
const canonicalUrl = buildAbsoluteUrl(url ?? router.asPath?.split('?')[0]);

return (
<Head>
<title>{resolvedTitle}</title>
<meta name="description" content={description} key="description" />
<link rel="canonical" href={canonicalUrl} key="canonical" />
{noindex && <meta name="robots" content="noindex, nofollow" key="robots" />}

<meta property="og:site_name" content={SITE_NAME} key="og:site_name" />
<meta property="og:type" content={type} key="og:type" />
<meta property="og:title" content={resolvedTitle} key="og:title" />
<meta property="og:description" content={description} key="og:description" />
<meta property="og:url" content={canonicalUrl} key="og:url" />
<meta property="og:image" content={image} key="og:image" />
{imageAlt && <meta property="og:image:alt" content={imageAlt} key="og:image:alt" />}

<meta name="twitter:card" content={TWITTER_CARD_TYPE} key="twitter:card" />
<meta name="twitter:title" content={resolvedTitle} key="twitter:title" />
<meta name="twitter:description" content={description} key="twitter:description" />
<meta name="twitter:image" content={image} key="twitter:image" />
</Head>
);
}
8 changes: 3 additions & 5 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useEffect } from 'react';
import type { NextPage } from 'next';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { useRouter } from 'next/router';
import './index.scss';
import { GoogleAnalytics, GoogleTagManager } from '@next/third-parties/google';
Expand All @@ -11,6 +10,7 @@ import Toast from 'components/feedback/Toast';
import Layout from 'components/layout';
import MaintenancePage from 'components/Maintenance';
import PortalProvider from 'components/modal/Modal/PortalProvider';
import Seo from 'components/seo/Seo';
import ROUTES from 'static/routes';
import { COOKIE_KEY } from 'static/url';
import useAutoLogin from 'utils/hooks/auth/useAutoLogin';
Expand Down Expand Up @@ -79,7 +79,7 @@ export default function App({ Component, pageProps }: AppPropsWithAuth) {
const getLayout = Component.getLayout || ((page) => <Layout>{page}</Layout>);

const pageTitle = React.useMemo(() => {
if (!Component.title) return 'KOIN';
if (!Component.title) return undefined;
return typeof Component.title === 'function' ? Component.title(router.asPath) : Component.title;
}, [Component, router.asPath]);

Expand Down Expand Up @@ -141,9 +141,7 @@ export default function App({ Component, pageProps }: AppPropsWithAuth) {
{GA_ID && <GoogleAnalytics gaId={GA_ID} />}

<PortalProvider>
<Head>
<title>{pageTitle}</title>
</Head>
<Seo title={pageTitle} />
<AutoLogin />
{getLayout(<Component {...pageProps} />)}
<Toast />
Expand Down
12 changes: 1 addition & 11 deletions src/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,10 @@ export default function Document() {
<Head>
<link rel="preconnect" href="https://static.koreatech.in" crossOrigin="anonymous" />

{/* 기본 메타 */}
{/* 기본 메타 (페이지 단위 메타는 components/seo/Seo 가 처리) */}
<meta charSet="utf-8" />
<meta name="theme-color" content="#000000" />
<meta name="author" content="BCSD Lab" />
<meta name="description" content="보다 편하게, 한국기술교육대학교 생활에 필요한 서비스를 만날 수 있습니다." />

{/* Open Graph */}
<meta property="og:title" content="코인 - 한기대 커뮤니티" />
<meta
property="og:description"
content="보다 편하게, 한국기술교육대학교 생활에 필요한 서비스를 만날 수 있습니다."
/>
<meta property="og:image" content="https://static.koreatech.in/assets/img/facebook_showcase_image.png" />
<meta property="og:image:width" content="1200" />

{/* 국제화/번역 제어 */}
<meta name="google" content="notranslate" />
Expand Down
15 changes: 15 additions & 0 deletions src/static/seo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { KOIN_BASE_URL } from 'static/url';

export const SITE_NAME = '코인';
export const DEFAULT_TITLE = '코인 - 한기대 커뮤니티';
export const DEFAULT_DESCRIPTION =
'보다 편하게, 한국기술교육대학교 생활에 필요한 서비스를 만날 수 있습니다.';
export const DEFAULT_OG_IMAGE = 'https://static.koreatech.in/assets/img/facebook_showcase_image.png';
export const TWITTER_CARD_TYPE = 'summary_large_image';

export const buildAbsoluteUrl = (path?: string) => {
if (!path) return KOIN_BASE_URL;
if (/^https?:\/\//.test(path)) return path;
const normalized = path.startsWith('/') ? path : `/${path}`;
return `${KOIN_BASE_URL}${normalized}`;
};
Loading