Skip to content

Commit 3227d48

Browse files
committed
Setup react-helmet for several pages & fix breaking test code
1 parent 9882e39 commit 3227d48

File tree

21 files changed

+190
-128
lines changed

21 files changed

+190
-128
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@types/react": "^16.9.0",
3838
"@types/react-beautiful-dnd": "^11.0.3",
3939
"@types/react-dom": "^16.9.0",
40+
"@types/react-helmet-async": "^1.0.3",
4041
"@types/react-outside-click-handler": "^1.3.0",
4142
"@types/react-redux": "^7.1.5",
4243
"@types/react-router-dom": "^5.1.3",
@@ -106,6 +107,7 @@
106107
"react-beautiful-dnd": "^12.2.0",
107108
"react-dev-utils": "^10.0.0",
108109
"react-dom": "^16.12.0",
110+
"react-helmet-async": "^1.0.4",
109111
"react-icons": "^3.8.0",
110112
"react-outside-click-handler": "^1.3.0",
111113
"react-redux": "^7.1.3",

src/App.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Suspense } from 'react';
1+
import React from 'react';
22
import { Route, Switch } from 'react-router-dom';
33
// import MainPage from './pages/main/MainPage';
44
// import PostPage from './pages/PostPage';
@@ -11,6 +11,7 @@ import PageTemplate from './components/base/PageTemplate';
1111
import VelogPageFallback from './containers/velog/VelogPageFallback';
1212
import ErrorBoundary from './components/error/ErrorBoundary';
1313
import NotFoundPage from './pages/NotFoundPage';
14+
import { Helmet } from 'react-helmet-async';
1415

1516
const loadableConfig = {
1617
fallback: <PageTemplate />,
@@ -45,6 +46,15 @@ interface AppProps {}
4546
const App: React.FC<AppProps> = props => {
4647
return (
4748
<JazzbarProvider>
49+
<Helmet>
50+
<title>velog</title>
51+
<meta
52+
name="description"
53+
content="개발자들을 위한 블로그 서비스. 어디서 글 쓸지 고민하지 말고 벨로그에서 시작하세요."
54+
/>
55+
<meta property="fb:app_id" content="203040656938507" />
56+
<meta property="og:image" content="https://images.velog.io/velog.png" />
57+
</Helmet>
4858
<ErrorBoundary>
4959
<Switch>
5060
<Route path="/" component={MainPage} exact />

src/components/base/__tests__/__snapshots__/HeaderUserIcon.test.tsx.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
exports[`HeaderUserIcon matches snapshot 1`] = `
44
<div>
55
<div
6-
class="sc-bdVaJa dKjqSX"
6+
class="sc-bdVaJa gdqjx"
77
>
88
<img
99
alt="thumbnail"
@@ -16,6 +16,7 @@ exports[`HeaderUserIcon matches snapshot 1`] = `
1616
stroke-width="0"
1717
viewBox="0 0 24 24"
1818
width="1em"
19+
xmlns="http://www.w3.org/2000/svg"
1920
>
2021
<path
2122
d="M7 10l5 5 5-5z"

src/components/common/__tests__/UserProfile.test.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import { render } from '@testing-library/react';
33
import UserProfile, { UserProfileProps } from '../UserProfile';
4+
import { MemoryRouter } from 'react-router-dom';
45

56
describe('UserProfile', () => {
67
const setup = (props: Partial<UserProfileProps> = {}) => {
@@ -14,8 +15,13 @@ describe('UserProfile', () => {
1415
1516
},
1617
thumbnail: 'https://via.placeholder.com/150',
18+
username: 'velopert',
1719
};
18-
const utils = render(<UserProfile {...initialProps} {...props} />);
20+
const utils = render(
21+
<MemoryRouter>
22+
<UserProfile {...initialProps} {...props} />
23+
</MemoryRouter>,
24+
);
1925
return {
2026
...utils,
2127
};

src/components/policy/PolicyViewer.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@ import React from 'react';
22
import data from './policyData';
33
import MarkdownRender from '../common/MarkdownRender';
44
import styled from 'styled-components';
5+
import { Helmet } from 'react-helmet-async';
56

67
export type PolicyViewerProps = {
78
type: 'privacy' | 'terms';
89
};
910

1011
function PolicyViewer({ type }: PolicyViewerProps) {
12+
const title = type === 'privacy' ? '개인정보 취급 방침' : '이용약관';
13+
1114
return (
1215
<Block>
16+
<Helmet>
17+
<title>{`${title} - velog`}</title>
18+
<meta name="robots" content="noindex" />
19+
</Helmet>
1320
<MarkdownRender markdown={data[type]} />
1421
</Block>
1522
);

src/components/velog/VelogAboutContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const EmptyAbout = styled.div`
2222
}
2323
.message {
2424
font-size: 2rem;
25-
color: ${palette.gray4};
25+
color: ${palette.gray6};
2626
margin-bottom: 2rem;
2727
}
2828
`;

src/components/velog/VelogTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import PlainNavLink from '../common/PlainNavLink';
55
import media from '../../lib/styles/media';
66

77
const VelogTabBlock = styled.div`
8-
margin-top: 6rem;
8+
margin-top: 4.5rem;
99
margin-bottom: 4.5rem;
1010
display: flex;
1111
justify-content: center;

src/containers/post/PostViewer.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useEffect, useCallback, useRef } from 'react';
2-
import { useDispatch } from 'react-redux';
1+
import React, { useEffect, useCallback, useRef, useMemo } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
33
import {
44
READ_POST,
55
SinglePost,
@@ -31,6 +31,8 @@ import { useMount } from 'react-use';
3131
import PostSkeleton from '../../components/post/PostSkeleton';
3232
import media from '../../lib/styles/media';
3333
import useNotFound from '../../lib/hooks/useNotFound';
34+
import { Helmet } from 'react-helmet-async';
35+
import { RootState } from '../../modules';
3436

3537
const UserProfileWrapper = styled(VelogResponsive)`
3638
margin-top: 16rem;
@@ -90,6 +92,11 @@ const PostViewer: React.FC<PostViewerProps> = ({
9092
const [likePost, { loading: loadingLike }] = useMutation(LIKE_POST);
9193
const [unlikePost, { loading: loadingUnlike }] = useMutation(UNLIKE_POST);
9294
const { showNotFound } = useNotFound();
95+
const userLogo = useSelector((state: RootState) => state.header.userLogo);
96+
const velogTitle = useMemo(() => {
97+
if (!userLogo || !userLogo.title) return `${username}.log`;
98+
return userLogo.title;
99+
}, [userLogo, username]);
93100

94101
const { error, data } = readPost;
95102

@@ -281,8 +288,32 @@ const PostViewer: React.FC<PostViewerProps> = ({
281288

282289
const { post } = data;
283290

291+
const url = `https://velog.io/@${username}/${post.url_slug}`;
292+
284293
return (
285294
<PostViewerProvider prefetchLinkedPosts={prefetchLinkedPosts}>
295+
<Helmet>
296+
<title>
297+
{post.title} - {velogTitle}
298+
</title>
299+
{post.short_description && (
300+
<meta name="description" content={post.short_description} />
301+
)}
302+
<link rel="canonical" href={url} />
303+
<meta property="og:url" content={url} />
304+
<meta property="og:type" content="article" />
305+
<meta property="og:title" content={post.title} />
306+
<meta property="og:description" content={post.short_description} />
307+
{post.thumbnail && (
308+
<meta property="og:image" content={post.thumbnail} />
309+
)}
310+
<meta name="twitter:card" content="summary_large_image" />
311+
<meta name="twitter:title" content={post.title} />
312+
<meta name="twitter:description" content={post.short_description} />
313+
{post.thumbnail && (
314+
<meta name="twitter:image" content={post.thumbnail} />
315+
)}
316+
</Helmet>
286317
<PostHead
287318
title={post.title}
288319
tags={post.tags}

src/containers/post/__tests__/PostViewer.test.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { InMemoryCache } from 'apollo-cache-inmemory';
88
import { ApolloClient } from 'apollo-client';
99
import { ApolloProvider } from '@apollo/react-hooks';
1010
import renderWithProviders from '../../../lib/renderWithProviders';
11+
import { HelmetProvider } from 'react-helmet-async';
1112

1213
const samplePost = {
1314
id: '6533da20-b351-11e8-9696-f1fffe8a36f1',
@@ -93,7 +94,9 @@ describe('PostViewer', () => {
9394
};
9495

9596
const utils = renderWithProviders(
96-
<PostViewer {...initialProps} {...props} />,
97+
<HelmetProvider>
98+
<PostViewer {...initialProps} {...props} />
99+
</HelmetProvider>,
97100
{
98101
mocks: overrideMocks,
99102
},

src/containers/search/SearchResult.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SearchPostsResponse, SEARCH_POSTS } from '../../lib/graphql/post';
66
import useScrollPagination from '../../lib/hooks/useScrollPagination';
77
import { safe } from '../../lib/utils';
88
import styled from 'styled-components';
9+
import { Helmet } from 'react-helmet-async';
910
// import { undrawSearching } from '../../static/images';
1011

1112
export interface SearchResultProps {
@@ -63,6 +64,9 @@ function SearchResult({ keyword, username }: SearchResultProps) {
6364

6465
return (
6566
<>
67+
<Helmet>
68+
<title>{`"${keyword}" 검색 결과 - velog`}</title>
69+
</Helmet>
6670
<SearchResultInfo count={data.searchPosts.count} />
6771
<PostCardList posts={data.searchPosts.posts} hideUser={!!username} />
6872
</>

src/containers/velog/UserProfileContainer.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import UserProfile, {
99
UserProfileSkeleton,
1010
} from '../../components/common/UserProfile';
1111
import media from '../../lib/styles/media';
12+
import { Helmet } from 'react-helmet-async';
1213

1314
export interface UserProfileContainerProps {
1415
username: string;
@@ -37,13 +38,19 @@ const UserProfileContainer: React.FC<UserProfileContainerProps> = ({
3738
} = data.user.profile;
3839

3940
return (
40-
<StyledUserProfile
41-
displayName={display_name}
42-
description={short_bio}
43-
profileLinks={profile_links}
44-
thumbnail={thumbnail}
45-
username={username}
46-
/>
41+
<>
42+
<Helmet>
43+
<title>{`${username} (${display_name}) - velog`}</title>
44+
<meta name="description" content={short_bio} />
45+
</Helmet>
46+
<StyledUserProfile
47+
displayName={display_name}
48+
description={short_bio}
49+
profileLinks={profile_links}
50+
thumbnail={thumbnail}
51+
username={username}
52+
/>
53+
</>
4754
);
4855
};
4956

src/containers/velog/VelogAbout.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import VelogAboutEdit from '../../components/velog/VelogAboutEdit';
1414
import VelogAboutRightButton from '../../components/velog/VelogAboutRightButton';
1515
import styled from 'styled-components';
1616
import media from '../../lib/styles/media';
17+
import { Helmet } from 'react-helmet-async';
18+
import strip from 'strip-markdown';
1719

1820
export interface VelogAboutProps {
1921
username: string;
@@ -45,6 +47,19 @@ const VelogAbout = ({ username }: VelogAboutProps) => {
4547

4648
return (
4749
<Block>
50+
<Helmet>
51+
<title>
52+
About {username} ({data.user.profile.display_name}) - velog
53+
</title>
54+
<meta
55+
name="description"
56+
content={
57+
data.user.profile.about
58+
? strip(data.user.profile.about)
59+
: `${username}님의 자기소개가 비어있습니다.`
60+
}
61+
/>
62+
</Helmet>
4863
{own && (data.user.profile.about || edit) && (
4964
<VelogAboutRightButton edit={edit} onClick={onClick} />
5065
)}

src/containers/velog/__tests__/ConfigLoader.test.tsx

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/containers/velog/__tests__/UserProfileContainer.test.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import UserProfileContainer, {
66
import { MockedResponse } from '@apollo/react-testing';
77
import { GET_USER_PROFILE } from '../../../lib/graphql/user';
88
import renderWithProviders from '../../../lib/renderWithProviders';
9+
import { HelmetProvider } from 'react-helmet-async';
910

1011
describe('UserProfileContainer', () => {
1112
const setup = (props: Partial<UserProfileContainerProps> = {}) => {
@@ -45,7 +46,9 @@ describe('UserProfileContainer', () => {
4546
];
4647

4748
const utils = renderWithProviders(
48-
<UserProfileContainer {...initialProps} {...props} />,
49+
<HelmetProvider>
50+
<UserProfileContainer {...initialProps} {...props} />
51+
</HelmetProvider>,
4952
{ mocks },
5053
);
5154
return {

0 commit comments

Comments
 (0)