From 5e1545e43a4503c31e5ff0c621d68acf85f53aae Mon Sep 17 00:00:00 2001 From: YUNHO Date: Thu, 16 Feb 2023 17:27:00 +0900 Subject: [PATCH] =?UTF-8?q?seo=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20og:image=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95=20(#31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: blog-config에 lang, mainOgImage 추가 * Refactor: SEOConfigType과 ConfigType분리 * Fix: GatsbyImageDataType제거 -> GatsbyImageDataType * Refactor: Image component에서 useStaticImageProps hooks분리 * Fix: GatsbyImageDataType제거 -> GatsbyImageDataType * Feat: seo컴포넌트에 useStaticImage 적용 * Docs: blog config에서 favicon을 제외한 이미지 경로제거 * Chore: main-og-image변경 * docs: 리드미 업데이트 --- README.md | 7 ++- blog-config.js | 4 +- src/components/Common/Image/index.tsx | 47 +-------------- .../PostDetail/PostDetail.style.tsx | 6 +- .../PostDetail/PostHeader/index.tsx | 4 +- src/components/PostDetail/index.tsx | 12 +--- src/components/SEO/index.tsx | 40 +++++++++---- src/hooks/useBlogConfig.ts | 3 +- src/hooks/useStaticImage.ts | 54 ++++++++++++++++++ src/pages/index.tsx | 13 +---- src/templates/post.template.tsx | 6 +- src/types/PostItem.types.ts | 4 +- src/types/gatsby.type.ts | 36 +++++++----- static/main-og-image.png | Bin 0 -> 8436 bytes 14 files changed, 129 insertions(+), 107 deletions(-) create mode 100644 src/hooks/useStaticImage.ts create mode 100644 static/main-og-image.png diff --git a/README.md b/README.md index 71c459e..97fc15e 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ SEO, 웹접근성 - [SEO, 웹 접근성](https://github.com/kimyouknow/kimyouknow.github.io/pull/15) - [Slack에 메타 태그가 적용되지 않는 버그 수정](https://github.com/kimyouknow/kimyouknow.github.io/pull/27) +- [SEO 컴포넌트 리팩토링 및 og:image 에러 수정](https://github.com/kimyouknow/kimyouknow.github.io/pull/31) 기타 @@ -113,13 +114,15 @@ Github에서 아래와 같이 세팅 후 원하는 main브랜치에 push하면 ```js { + lang: "", // ko title: ``, //Yunho.blog author: '', // Yunho(kimyouknow) description: ``, // 안녕하세요. 프론트엔드 개발자 김윤호입니다. 고민과 문제 해결 과정을 공유하고 있습니다. siteUrl: '', // https://kimyouknow.github.io/ - image: ``, // static 경로에 원하는 사진을 넣어주시면 프로필 이미지로 반영됩니다. ex) ./static/profile-image.png + profileImage: ``, // static 경로에 있는 사진 파일을 입력하면 프로필 이미지로 반영됩니다. ex) profile-image.png + mainOgImage :"", // static 경로에 원하는 사진 파일을 입력하면 메인페이지의 og-image 태그로 반영됩니다. keywords: [], // 원하는 키워드를 적어주시면 keywords meta태그에 반영됩니다. ex) '개발블로그', '문제해결', 'gatsby' - favicon: '', // static 경로에 원하는 사진을 넣어주시면 favicon 이미지로 반영됩니다. ex) ./static/pencil.png + favicon: '', // static 경로에 원하는 사진을 넣어주시면 favicon 이미지로 반영됩니다. ex) /static/pencil.png social: { email: '', // kimyouknow@naver.com github: ``, //https://github.com/kimyouknow diff --git a/blog-config.js b/blog-config.js index c83677d..118ed65 100644 --- a/blog-config.js +++ b/blog-config.js @@ -3,11 +3,13 @@ require('dotenv').config({ }) module.exports = { + lang: 'ko', siteName: `Yunho.blog`, author: 'Yunho(kimyouknow)', description: `안녕하세요. 프론트엔드 개발자 김윤호입니다. 고민과 문제 해결 과정을 공유하고 있습니다.`, siteUrl: 'https://kimyouknow.github.io', - image: `static/profile-image.png`, + profileImage: `profile-image.png`, + mainOgImage: 'main-og-image.png', keywords: ['개발블로그', '문제해결', 'gatsby'], favicon: 'static/pencil.png', social: { diff --git a/src/components/Common/Image/index.tsx b/src/components/Common/Image/index.tsx index b10e12f..e3e9560 100644 --- a/src/components/Common/Image/index.tsx +++ b/src/components/Common/Image/index.tsx @@ -1,9 +1,7 @@ import styled from '@emotion/styled' -import { graphql, useStaticQuery } from 'gatsby' import { GatsbyImage, IGatsbyImageData } from 'gatsby-plugin-image' -import { useMemo } from 'react' -import { GatsbyImageDataType } from '@/types/gatsby.type' +import useStaticImage from '@/hooks/useStaticImage' const ImageSizeMap = { s: 'var(--icon-medium)', @@ -19,36 +17,14 @@ interface ImageProps { isCircle?: boolean } -interface ImageNode { - node: { - relativePath: string - extension: string - publicURL: string - childImageSharp: { - gatsbyImageData: IGatsbyImageData - } - } -} - -interface AssetsImageType { - images: { - edges: ImageNode[] - } -} - interface GatsbyImgProps extends Omit { - image: GatsbyImageDataType + image: IGatsbyImageData alt: string className?: string } const Image = ({ src, size = 'm', isCircle = false, ...rest }: ImageProps) => { - const assetImages = useStaticQuery(imageQuery) - - const target = useMemo( - () => assetImages.images.edges.find(({ node }) => src === node.relativePath), - [assetImages, src], - ) + const target = useStaticImage({ src }) if (!target) return null @@ -70,20 +46,3 @@ const SImage = styled(({ isCircle, ...rest }: GatsbyImgProps) => { diff --git a/src/components/PostDetail/index.tsx b/src/components/PostDetail/index.tsx index 461d31f..f2b7749 100644 --- a/src/components/PostDetail/index.tsx +++ b/src/components/PostDetail/index.tsx @@ -1,6 +1,5 @@ import PostFooter from '@/components/PostDetail/PostFooter' import SEO from '@/components/SEO' -import useBlogConfig from '@/hooks/useBlogConfig' import Layout from '@/Layout' import { PostPageItemType } from '@/types/PostItem.types' @@ -9,11 +8,10 @@ import PostHeader from './PostHeader' interface PostPageInfoProps { postPageInfo: PostPageItemType - href: string + pathname: string } -const PostDetail = ({ postPageInfo, href }: PostPageInfoProps) => { - const { author, favicon, seo, siteName } = useBlogConfig() +const PostDetail = ({ postPageInfo, pathname }: PostPageInfoProps) => { const { node: { tableOfContents, @@ -34,15 +32,11 @@ const PostDetail = ({ postPageInfo, href }: PostPageInfoProps) => { return ( { - const absoluteImagePath = `${siteUrl}/${image}` + const { + lang, + author, + siteName, + favicon, + seo, + siteUrl, + mainOgImage, + description: mainDesc, + keywords: mainKeywords, + } = useBlogConfig() + const target = useStaticImage({ src: mainOgImage }) + const mainOgImagePath = target?.node.publicURL || '' + // params가 없으면 기본 config type을 사용하기 + const pageUrl = `${siteUrl}${pathname || ``}` + keywords = keywords || mainKeywords + description = description || mainDesc + image = image ? `${siteUrl}${image}` : `${siteUrl}/${mainOgImagePath}` + title = title || siteName return ( {title} @@ -31,16 +47,16 @@ const SEO = ({ - - + + {/* twitter cards tags additive with th og: tags */} - + - + diff --git a/src/hooks/useBlogConfig.ts b/src/hooks/useBlogConfig.ts index da1d178..349a72d 100644 --- a/src/hooks/useBlogConfig.ts +++ b/src/hooks/useBlogConfig.ts @@ -18,7 +18,8 @@ const useBlogConfig = () => { siteName description siteUrl - image + profileImage + mainOgImage keywords favicon social { diff --git a/src/hooks/useStaticImage.ts b/src/hooks/useStaticImage.ts new file mode 100644 index 0000000..68a3c3b --- /dev/null +++ b/src/hooks/useStaticImage.ts @@ -0,0 +1,54 @@ +import { graphql, useStaticQuery } from 'gatsby' +import { IGatsbyImageData } from 'gatsby-plugin-image' +import { useMemo } from 'react' + +interface ImageNode { + node: { + relativePath: string + extension: string + publicURL: string + childImageSharp: { + gatsbyImageData: IGatsbyImageData + } + } +} + +interface AssetsImageType { + images: { + edges: ImageNode[] + } +} + +export interface useStaticImageProps { + src: string +} + +const useStaticImage = ({ src }: useStaticImageProps) => { + const assetImages = useStaticQuery(imageQuery) + + const target = useMemo( + () => assetImages.images.edges.find(({ node }) => src === node.relativePath), + [assetImages, src], + ) + + return target +} + +export default useStaticImage + +const imageQuery = graphql` + query { + images: allFile(filter: { sourceInstanceName: { eq: "static" } }) { + edges { + node { + relativePath + extension + publicURL + childImageSharp { + gatsbyImageData(layout: CONSTRAINED) + } + } + } + } + } +` diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 697e1c0..50fde90 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,7 +6,6 @@ import MainHeader from '@/components/CategoryHeader' import { CategoryListProps } from '@/components/CategoryHeader/CategoryList' import PostList from '@/components/PostList' import SEO from '@/components/SEO' -import useBlogConfig from '@/hooks/useBlogConfig' import Layout from '@/Layout' import { PostListItemType, PostType } from '@/types/PostItem.types' @@ -27,7 +26,6 @@ const IndexPage = ({ allMarkdownRemark: { edges }, }, }: IndexPageProps) => { - const { author, siteName, siteUrl, description, image, keywords, favicon, seo } = useBlogConfig() const parsed: ParsedQuery = queryString.parse(hash) const selectedCategory = typeof parsed.category !== 'string' || !parsed.category ? 'All' : parsed.category // category 프로퍼티 값이 문자열 형태가 아니거나 존재하지 않는 경우에는 기본적으로 카테고리 값을 All로 지정하고, 그러지 않은 경우에는 파싱한 값을 지정 @@ -57,16 +55,7 @@ const IndexPage = ({ ) return ( - + diff --git a/src/templates/post.template.tsx b/src/templates/post.template.tsx index 352c4dd..a0fb9a2 100644 --- a/src/templates/post.template.tsx +++ b/src/templates/post.template.tsx @@ -11,7 +11,7 @@ interface PostTemplateProps { } } location: { - href: string + pathname: string } } @@ -19,9 +19,9 @@ const PostTemplate = ({ data: { allMarkdownRemark: { edges }, }, - location: { href }, + location: { pathname }, }: PostTemplateProps) => { - return + return } export default PostTemplate diff --git a/src/types/PostItem.types.ts b/src/types/PostItem.types.ts index 1b847ba..55a3bff 100644 --- a/src/types/PostItem.types.ts +++ b/src/types/PostItem.types.ts @@ -1,4 +1,4 @@ -import { GatsbyImageDataType } from './gatsby.type' +import { IGatsbyImageData } from 'gatsby-plugin-image' export interface PostFrontmatterType { title: string @@ -7,7 +7,7 @@ export interface PostFrontmatterType { summary: string thumbnail: { childImageSharp: { - gatsbyImageData: GatsbyImageDataType + gatsbyImageData: IGatsbyImageData } publicURL: string } diff --git a/src/types/gatsby.type.ts b/src/types/gatsby.type.ts index abe9be0..3d45658 100644 --- a/src/types/gatsby.type.ts +++ b/src/types/gatsby.type.ts @@ -1,36 +1,42 @@ import { IGatsbyImageData } from 'gatsby-plugin-image' -export type GatsbyImageDataType = IGatsbyImageData - export interface GatsbyImgProps { - image: GatsbyImageDataType + image: IGatsbyImageData alt: string className?: string } export interface SEOConfigType { - lang?: string + title?: string + pathname?: string + description?: string + image?: string + keywords?: string[] + readingTime?: string +} + +/** + * blog-config.js의 정보 타입 + */ +export interface ConfigType { + lang: 'ko' | 'en' author: string siteName: string - siteUrl: string - title?: string description: string - image: string + siteUrl: string + profileImage: string + mainOgImage: string keywords: string[] favicon: string - seo: { - google: string - naver: string - } - readingTime?: string -} - -export interface ConfigType extends SEOConfigType { social: { email: string github: string til: string } + seo: { + google: string + naver: string + } utterances: { src: string repo: string diff --git a/static/main-og-image.png b/static/main-og-image.png new file mode 100644 index 0000000000000000000000000000000000000000..d623f583bcd340d8e8b774d208e76d97de6b7d7b GIT binary patch literal 8436 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 literal 0 HcmV?d00001