Skip to content

Commit

Permalink
Feat/#38: SEO 최적화 및 메타 태그 적용 (#44)
Browse files Browse the repository at this point in the history
* feat: 파비콘 적용

* refactor: 불필요한 cdn 제거

* feat: title, description, keyword SEO 적용

* chore: react-helmet-async 설치

* rename: favicon 디렉토리 위치 변경

* feat: og image, locale, url, type, site_name 적용

title과 description은 helmet을 활용하여 동적 적용한다.

* feat: SEO meta 데이터 동적 삽입 컴포넌트 구현

* refactor: 필수 props 에 기본값 제거

* feat: FeedDetailPage Helmet 적용

* chore: react-helmet-async install yarn.lock 반영

* rename: HelmetMeta -> SEOMeta로 네이밍 변경

* refactor: og title, description 은 React Helmet을 통해 동적 생성

* feat: SEO Meta 태그 각 페이지별로 적용
  • Loading branch information
semnil5202 authored Mar 13, 2024
1 parent 2ba78fa commit 86096d6
Show file tree
Hide file tree
Showing 18 changed files with 573 additions and 447 deletions.
36 changes: 22 additions & 14 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
<!doctype html>
<html lang="en">
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/public/assets/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>컨셉비 - 당신의 상상이 현실로!</title>
<meta
name="description"
content="아이디어 기반의 안전하고 자유로운 팀원 찾기 플랫폼 (창업, 유튜브, 콘텐츠, 사이드프로젝트, 공모전, 스터디)"
/>
<meta name="keyword" content="팀원, 모집, 리크루팅, 플랫폼, 유튜브, 사이드프로젝트, 취미, 공모전, IT" />

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link href="https://cdn.jsdelivr.net/gh/sunn-us/SUIT/fonts/variable/woff2/SUIT-Variable.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<meta property="og:image" content="/assets/conceptbe_og_image.png" />
<meta property="og:locale" content="ko_KR" />
<meta property="og:url" content="http://conceptbe.kr/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="컨셉비" />
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"concept-be-design-system": "^0.4.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.4",
"react-router-dom": "^6.15.0",
"react-use": "^17.5.0",
"zustand": "^4.4.1"
Expand Down
3 changes: 0 additions & 3 deletions public/_redirects

This file was deleted.

Binary file added public/assets/conceptbe_og_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

18 changes: 18 additions & 0 deletions src/components/SEOMeta/SEOMeta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Helmet } from 'react-helmet-async';

interface Props {
title: string;
description: string;
}

const SEOMeta = ({ title, description }: Props) => {
return (
<Helmet>
<title>{title}</title>
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
</Helmet>
);
};

export default SEOMeta;
23 changes: 13 additions & 10 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { OverlayProvider } from '@toss/use-overlay';
import { ConceptBeProvider } from 'concept-be-design-system';
import { createRoot } from 'react-dom/client';
import { HelmetProvider } from 'react-helmet-async';

import App from './App.tsx';
import './styles/reset.css';
Expand All @@ -11,14 +12,16 @@ import GlobalErrorBoundary from './components/ErrorBoundary/GlobalErrorBoundary.
const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<ConceptBeProvider>
<OverlayProvider>
<GlobalErrorBoundary>
<App />
</GlobalErrorBoundary>
</OverlayProvider>
</ConceptBeProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>,
<HelmetProvider>
<QueryClientProvider client={queryClient}>
<ConceptBeProvider>
<OverlayProvider>
<GlobalErrorBoundary>
<App />
</GlobalErrorBoundary>
</OverlayProvider>
</ConceptBeProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</HelmetProvider>,
);
7 changes: 3 additions & 4 deletions src/pages/Feed/Feed.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import FilterBottomSheet from './components/FilterBottomSheet/FilterBottomSheet'
import NewIdeaCardListSection from './components/NewIdeaCardListSection/NewIdeaCardListSection';
import { getUserNickname } from './utils/getUserNickname';
import Padding from '../../components/Padding';
import SEOMeta from '../../components/SEOMeta/SEOMeta';
import Logo from '../../layouts/Logo';
import { useWritingInfoQuery } from '../Write/hooks/queries/useWritingInfoQuery';

Expand All @@ -24,6 +25,8 @@ const Feed = () => {

return (
<>
<SEOMeta title="컨셉비 | 전체 피드" description="자유롭고 안전한 아이디어 공유의 장" />

<Header main>
<Header.Item>
<Logo />
Expand Down Expand Up @@ -94,10 +97,6 @@ const FeedFixBox = styled.div`
color: ${theme.color.w1};
`;

const FeedWrapper = styled.div`
padding-top: 47px;
`;

const FeedFixTextWrapper = styled.div`
display: flex;
gap: 5px;
Expand Down
3 changes: 3 additions & 0 deletions src/pages/FeedDetail/FeedDetail.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ReactionBar from './components/ReactionBar';
import { CommentFocusProvider } from './contexts/CommentFocusContext';
import useFeedDetailQuery from './hooks/queries/useFeedDetailQuery';
import ProfileInfo from '../../components/ProfileInfo';
import SEOMeta from '../../components/SEOMeta/SEOMeta';
import Back from '../../layouts/Back';
import Logo from '../../layouts/Logo';
import { formatCommentDate } from '../Feed/utils/formatCommentDate';
Expand Down Expand Up @@ -46,6 +47,8 @@ const FeedDetailPage = () => {

return (
<CommentFocusProvider>
<SEOMeta title="컨셉비 | 글 상세" description={title} />

<Header main>
<Back />
<Logo />
Expand Down
163 changes: 84 additions & 79 deletions src/pages/Login/Agreement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BottomSheet, Button, Spacer, Text, PNGAgreementBackground, Flex } from
import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

import SEOMeta from '../../components/SEOMeta/SEOMeta';
import Privacy from '../../components/Terms/Privacy';
import UsageTerms from '../../components/Terms/UsageTerms';
import { OauthMemberInfo } from '../../types/login';
Expand All @@ -28,89 +29,93 @@ const Agreement = () => {
};

return (
<Wrapper height="100%" direction="column" padding="20px 22px">
<img src={PNGAgreementBackground} />
<Spacer size={50} />
<>
<SEOMeta title="컨셉비 | 약관동의" description="컨셉비 시작하기 (로그인/회원가입)" />

<Text
font="suit22sb"
color="b2"
style={{
lineHeight: 'normal',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
컨셉BE는 <br />
<Flex>
<Text font="suit22sb" color="c1">
아이디어의 독창성
</Text>
</Flex>
보호하는 플랫폼입니다.
</Text>
<Spacer size={20} />
<Text
font="suit14r"
color="b6"
style={{
lineHeight: '22px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
게시글을 조회하면 아이디어를 확인하였다는 <br /> 사실이 자동으로 데이터베이스에 저장됩니다. <br />
아이디어 표절이나 도용 시 해당 사실을 근거로 <br />
불이익이 발생할 수 있습니다.
</Text>
<Wrapper height="100%" direction="column" padding="20px 22px">
<img src={PNGAgreementBackground} />
<Spacer size={50} />

<DynamicSpacer size={50} />
<Text
font="suit22sb"
color="b2"
style={{
lineHeight: 'normal',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
컨셉BE는 <br />
<Flex>
<Text font="suit22sb" color="c1">
아이디어의 독창성
</Text>
</Flex>
보호하는 플랫폼입니다.
</Text>
<Spacer size={20} />
<Text
font="suit14r"
color="b6"
style={{
lineHeight: '22px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
게시글을 조회하면 아이디어를 확인하였다는 <br /> 사실이 자동으로 데이터베이스에 저장됩니다. <br />
아이디어 표절이나 도용 시 해당 사실을 근거로 <br />
불이익이 발생할 수 있습니다.
</Text>

<Text
font="suit13m"
color="b4"
style={{
display: 'flex',
flexDirection: 'column',
lineHeight: '150%',
alignItems: 'center',
}}
>
<Flex>
<Text
font="suit13m"
color="c1"
onClick={onClickOpenPrivacy}
style={{ cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: '2px' }}
>
개인정보 수집 및 이용 동의
</Text>
&nbsp; 및
</Flex>
<Flex>
<Text
font="suit13m"
color="c1"
onClick={onClickOpenUsageTerms}
style={{ cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: '2px' }}
>
서비스 이용약관
</Text>
에 동의하시나요?
</Flex>
</Text>
<Spacer size={24} />
<Button onClick={() => navigate('/sign-up', { state: memberInfo })}>동의하고 시작하기</Button>
<DynamicSpacer size={50} />

<BottomSheet isOpen={isOpen} onClose={onCloseBottomSheet}>
{isOpenPrivacy && <Privacy onClose={onCloseBottomSheet} />}
{!isOpenPrivacy && <UsageTerms onClose={onCloseBottomSheet} />}
</BottomSheet>
</Wrapper>
<Text
font="suit13m"
color="b4"
style={{
display: 'flex',
flexDirection: 'column',
lineHeight: '150%',
alignItems: 'center',
}}
>
<Flex>
<Text
font="suit13m"
color="c1"
onClick={onClickOpenPrivacy}
style={{ cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: '2px' }}
>
개인정보 수집 및 이용 동의
</Text>
&nbsp; 및
</Flex>
<Flex>
<Text
font="suit13m"
color="c1"
onClick={onClickOpenUsageTerms}
style={{ cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: '2px' }}
>
서비스 이용약관
</Text>
에 동의하시나요?
</Flex>
</Text>
<Spacer size={24} />
<Button onClick={() => navigate('/sign-up', { state: memberInfo })}>동의하고 시작하기</Button>

<BottomSheet isOpen={isOpen} onClose={onCloseBottomSheet}>
{isOpenPrivacy && <Privacy onClose={onCloseBottomSheet} />}
{!isOpenPrivacy && <UsageTerms onClose={onCloseBottomSheet} />}
</BottomSheet>
</Wrapper>
</>
);
};

Expand Down
Loading

0 comments on commit 86096d6

Please sign in to comment.