refactor: using next-seo for seo

This commit is contained in:
wonderlandpark 2021-05-22 01:56:35 +09:00
parent 84e6311a83
commit f4ba609f71
27 changed files with 117 additions and 67 deletions

View File

@ -4,16 +4,16 @@ import Link from 'next/link'
import { ReactNode, useState } from 'react'
import { DocsData } from '@types'
import { NextSeo } from 'next-seo'
const Container = dynamic(() => import('@components/Container'))
const Divider = dynamic(() => import('@components/Divider'))
const SEO = dynamic(() => import('@components/SEO'))
const DeveloperLayout: React.FC<DeveloperLayout> = ({ children, enabled, docs, currentDoc }:DeveloperLayout) => {
const [ navbarEnabled, setNavbarOpen ] = useState(false)
return <div className='flex min-h-screen'>
<SEO title='한디리 개발자' description='한국 디스코드봇 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' />
<NextSeo title='한디리 개발자' description='한국 디스코드봇 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' />
<div className='block lg:hidden h-screen relative'>
<div className='w-18 pt-20 px-2 h-full text-center bg-little-white dark:bg-discord-black fixed'>
<ul className='text-gray-600 dark:text-gray-300'>

View File

@ -1,15 +1,12 @@
import { NextSeo } from 'next-seo'
import dynamic from 'next/dynamic'
const Container = dynamic(() => import('@components/Container'))
const SEO = dynamic(() => import('@components/SEO'))
const Docs: React.FC<DocsProps> = ({ title, header, description, subheader, children }) => {
return (
<>
<SEO
title={typeof header === 'string' ? header : title}
description={description || subheader}
/>
<NextSeo title={typeof header === 'string' ? header : title} description={description || subheader}/>
<div className='dark:bg-discord-black bg-discord-blurple z-20'>
<Container className='py-20' ignoreColor>
<h1 className='mt-10 text-center text-gray-100 text-4xl font-bold sm:text-left'>

View File

@ -1,15 +1,15 @@
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo'
import { ErrorText } from '@utils/Constants'
const SEO = dynamic(() => import('@components/SEO'))
const Button = dynamic(() => import('@components/Button'))
const Forbidden:React.FC = () => {
const router = useRouter()
return <>
<SEO title='권한이 없습니다' />
<NextSeo title='권한이 없습니다' />
<div className='flex items-center justify-center h-screen select-none'>
<div className='container mx-auto px-20 md:text-left text-center'>
<h1 className='text-8xl font-semibold'>403</h1>

View File

@ -1,4 +1,5 @@
import dynamic from 'next/dynamic'
import { NextSeo } from 'next-seo'
import { categories, categoryIcon } from '@utils/Constants'
@ -8,6 +9,7 @@ const Search = dynamic(()=> import('@components/Search'))
const Hero:React.FC<HeroProps> = ({ header, description }) => {
return <>
<NextSeo title={header} description={description} />
<div className='dark:bg-discord-black bg-discord-blurple text-gray-100 md:p-0 mb-8'>
<Container className='pt-24 pb-16 md:pb-20' ignoreColor>
<h1 className='hidden md:block text-left text-3xl font-bold'>

View File

@ -5,7 +5,6 @@ const SEO: React.FC<SEOProps> = ({ title, description, image }: SEOProps) => {
<Head>
<title>{title} - </title>
{description && <meta name='description' content={description} />}
<meta name='og:site_name' content='한국 디스코드봇 리스트' />
<meta name='og:title' content={title} />
{description && <meta name='og:description' content={description} />}
{image && <meta name='og:image' content={image} />}

View File

@ -42,6 +42,7 @@
"next": "10.2.0",
"next-connect": "0.10.1",
"next-pwa": "5.2.21",
"next-seo": "4.24.0",
"next-session": "3.4.0",
"node-emoji": "1.10.0",
"nprogress": "0.2.0",

View File

@ -3,6 +3,7 @@ import App, { AppContext, AppProps } from 'next/app'
import dynamic from 'next/dynamic'
import { Router, useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { DefaultSeo } from 'next-seo'
import { GlobalHotKeys } from 'react-hotkeys'
import NProgress from 'nprogress'
@ -59,17 +60,36 @@ const KoreanbotsApp = ({ Component, pageProps, err, cookie }: KoreanbotsProps):
}, [])
return <div className={theme}>
<DefaultSeo
titleTemplate='%s - 한국 디스코드봇 리스트'
defaultTitle={TITLE}
description={DESCRIPTION}
openGraph={{
type: 'website',
title: TITLE,
url: 'https://koreanbots.dev',
site_name: TITLE,
description: DESCRIPTION,
images: [
{
url: '/logo.png',
width: 300,
height: 300,
alt: 'Logo'
}
]
}}
twitter={{
site: '@koreanbots',
handle: '@koreanbots',
cardType: 'summary'
}}
/>
<Head>
<title>{TITLE}</title>
{/* META */}
<meta charSet='utf-8' />
<meta httpEquiv='X-UA-Compatible' content='IE=edge' />
<meta name='description' content={DESCRIPTION} />
<meta name='keywords' content='Korea, Korean, Discord, Bot, 디스코드봇, 한디리' />
<meta name='og:title' content={TITLE} />
<meta name='og:url' content='https://koreanbots.dev' />
<meta name='og:description' content={DESCRIPTION} />
<meta name='og:image' content='/favicon.ico' />
<meta name='viewport' content='width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no' />
{/* Android */}

View File

@ -3,6 +3,7 @@ import { useRef, useState } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { NextSeo } from 'next-seo'
import { Form, Formik } from 'formik'
import HCaptcha from '@hcaptcha/react-hcaptcha'
@ -10,9 +11,9 @@ import { get } from '@utils/Query'
import { cleanObject, parseCookie, redirectTo } from '@utils/Tools'
import { AddBotSubmit, AddBotSubmitSchema } from '@utils/Yup'
import { categories, library } from '@utils/Constants'
import { ResponseProps, SubmittedBot, Theme, User } from '@types'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
import { ResponseProps, SubmittedBot, Theme, User } from '@types'
const CheckBox = dynamic(() => import('@components/Form/CheckBox'))
const Label = dynamic(() => import('@components/Form/Label'))
@ -27,7 +28,6 @@ const Button = dynamic(() => import('@components/Button'))
const Container = dynamic(() => import('@components/Container'))
const Message = dynamic(() => import('@components/Message'))
const Captcha = dynamic(() => import('@components/Captcha'))
const SEO = dynamic(() => import('@components/SEO'))
const AddBot:NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
const [ data, setData ] = useState<ResponseProps<SubmittedBot>>(null)
@ -68,10 +68,10 @@ const AddBot:NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
}
if(!logged) {
toLogin()
return <SEO title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드봇 리스트에 등록하세요.'/>
return <NextSeo title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드봇 리스트에 등록하세요.' />
}
return <Container paddingTop className='py-5'>
<SEO title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드봇 리스트에 등록하세요.'/>
<NextSeo title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드봇 리스트에 등록하세요.' />
<h1 className='text-3xl font-bold'> </h1>
<div className='mt-1 mb-5'>
, <span className='font-semibold'>{user.username}#{user.tag}</span>! <a role='button' tabIndex={0} onKeyDown={toLogin} onClick={toLogin} className='text-discord-blurple cursor-pointer outline-none'> ?</a>

View File

@ -2,6 +2,7 @@ import { NextPage, NextPageContext } from 'next'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { NextSeo } from 'next-seo'
import { useEffect, useState } from 'react'
import { Field, Form, Formik } from 'formik'
import Tooltip from 'rc-tooltip'
@ -26,7 +27,6 @@ const Divider = dynamic(() => import('@components/Divider'))
const Tag = dynamic(() => import('@components/Tag'))
const Owner = dynamic(() => import('@components/Owner'))
const Segment = dynamic(() => import('@components/Segment'))
const SEO = dynamic(() => import('@components/SEO'))
const LongButton = dynamic(() => import('@components/LongButton'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const Markdown = dynamic(() => import ('@components/Markdown'))
@ -49,10 +49,22 @@ const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken })
if (!data?.id) return <NotFound />
return <div style={bg ? { background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${data.bg}") center top / cover no-repeat fixed` } : {}}>
<Container paddingTop className='py-10'>
<SEO
<NextSeo
title={data.name}
description={data.intro}
image={KoreanbotsEndPoints.OG.bot(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.servers)])}
twitter={{
cardType: 'summary_large_image'
}}
openGraph={{
images: [
{
url: KoreanbotsEndPoints.OG.bot(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.servers)]),
width: 2048,
height: 1170,
alt: 'Bot Preview Image'
}
]
}}
/>
{
data.state === 'blocked' ? <div className='pb-40'>
@ -120,7 +132,7 @@ const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken })
</h4>
</LongButton>
}
<Link href={{ pathname: `/bots/${router.query.id}/vote`, query: { csrfToken } }}>
<Link href={`/bots/${router.query.id}/vote`}>
<LongButton>
<h4>
<i className='fas fa-heart text-red-600' />

View File

@ -17,6 +17,7 @@ import Fetch from '@utils/Fetch'
import Day from '@utils/Day'
import { getJosaPicker } from 'josa'
import { KoreanbotsEndPoints } from '@utils/Constants'
import { NextSeo } from 'next-seo'
const Container = dynamic(() => import('@components/Container'))
@ -24,26 +25,39 @@ const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar'))
const Button = dynamic(() => import('@components/Button'))
const Tag = dynamic(() => import('@components/Tag'))
const Segment = dynamic(() => import('@components/Segment'))
const SEO = dynamic(() => import('@components/SEO'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const Login = dynamic(() => import('@components/Login'))
const VoteBot: NextPage<VoteBotProps> = ({ data, user, csrfToken, theme }) => {
const VoteBot: NextPage<VoteBotProps> = ({ data, user, theme, csrfToken }) => {
const [ votingStatus, setVotingStatus ] = useState(0)
const [ result, setResult ] = useState<ResponseProps<{retryAfter?: number}>>(null)
const router = useRouter()
if(!data?.id) return <NotFound />
if(typeof window !== 'undefined' && csrfToken !== router.query.csrfToken) {
router.push(`/bots/${data.id}`)
return <SEO title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요.`} image={KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 })} />
}
if(!user) return <Login>
<SEO title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요.`} image={KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 })} />
<NextSeo title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요.`} openGraph={{
images: [
{
url: KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 }),
width: 256,
height: 256,
alt: 'Bot Avatar'
}
]
}} />
</Login>
if((checkBotFlag(data.flags, 'trusted') || checkBotFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/bots/${data.vanity}/vote?csrfToken=${csrfToken}`)
return <Container paddingTop className='py-10'>
<SEO title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요.`} image={KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 })} />
<NextSeo title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요.`} openGraph={{
images: [
{
url: KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 }),
width: 256,
height: 256,
alt: 'Bot Avatar'
}
]
}} />
<Advertisement />
<Link href={makeBotURL(data)}>
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)} </a>

View File

@ -1,13 +1,14 @@
import { GetServerSideProps, NextPage } from 'next'
import { useState } from 'react'
import dynamic from 'next/dynamic'
import { DiscordEnpoints, GuildPermissions } from '@utils/Constants'
import { NextSeo } from 'next-seo'
import { Formik, Form } from 'formik'
import { ParsedUrlQuery } from 'node:querystring'
import { ParsedUrlQuery } from 'querystring'
import { DiscordEnpoints, GuildPermissions } from '@utils/Constants'
const Container = dynamic(() => import('@components/Container'))
const Input = dynamic(() => import('@components/Form/Input'))
const SEO = dynamic(() => import('@components/SEO'))
const Calculator:NextPage<CalculatorProps> = ({ query }) => {
const [ value, setValue ] = useState<{[perm: string]: boolean}>({})
@ -23,7 +24,7 @@ const Calculator:NextPage<CalculatorProps> = ({ query }) => {
}
return <Container paddingTop className='pb-10'>
<SEO title='봇 초대링크 생성기' description='디스코드 봇 초대링크를 간편하게 생성하세요' />
<NextSeo title='봇 초대링크 생성기' description='디스코드 봇 초대링크를 간편하게 생성하세요' />
<h1 className='text-4xl font-bold mt-2 mb-4'> </h1>
<div className='text-2xl font-bold inline-flex items-center'>: {Object.keys(value).filter(el => value[el]).map(el => Number(el)).reduce((prev, curr) => prev | curr, 0)}
<span className='ml-2 text-lg font-semibold'>= { Object.keys(value).filter(el => value[el]).map(el => `0x${Number(el).toString(16)}`).join(' | ') }</span>

View File

@ -11,7 +11,6 @@ import { useRouter } from 'next/router'
const Hero = dynamic(() => import('@components/Hero'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const SEO = dynamic(() => import('@components/SEO'))
const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
const BotCard = dynamic(() => import('@components/BotCard'))
const Container = dynamic(() => import('@components/Container'))
@ -29,7 +28,6 @@ const Category: NextPage<CategoryProps> = ({ data, query }) => {
if(!data || data.data.length === 0 || data.totalPage < Number(query.page)) return <NotFound message={data?.data.length === 0 ? '해당 카테고리에 해당되는 봇이 존재하지 않습니다.' : null} />
return <>
<Hero header={`${query.category} 카테고리 봇들`} description={`다양한 "${query.category}" 카테고리의 봇들을 만나보세요.`} />
<SEO title={`${query.category} 카테고리 봇들`} description={`다양한 ${query.category} 카테고리의 봇들을 만나보세요.`} />
{
query.category === 'NSFW' && !nsfw ? <NSFW onClick={() => setNSFW(true)} onDisableClick={() => localStorage.nsfw = true} />
: <Container>

View File

@ -1,17 +1,18 @@
import { NextPage } from 'next'
import dynamic from 'next/dynamic'
import { NextSeo } from 'next-seo'
import { categories, categoryIcon } from '@utils/Constants'
const Container = dynamic(() => import('@components/Container'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const Tag = dynamic(() => import('@components/Tag'))
const SEO = dynamic(() => import('@components/SEO'))
const Segment = dynamic(() => import('@components/Segment'))
const Categories:NextPage = () => {
return <Container paddingTop>
<SEO title='전체 카테고리' description='한국 디스코드봇 리스트의 전체 카테고리입니다.'/>
<NextSeo title='전체 카테고리' description='한국 디스코드봇 리스트의 전체 카테고리입니다.' />
<h1 className='text-2xl font-bold mt-2 mb-5'> </h1>
<Segment className='mb-10'>
<div className='text-center flex flex-wrap mt-1.5'>

View File

@ -1,5 +1,6 @@
import { NextPage, NextPageContext } from 'next'
import dynamic from 'next/dynamic'
import { NextSeo } from 'next-seo'
import { get } from '@utils/Query'
import { parseCookie } from '@utils/Tools'
@ -8,12 +9,11 @@ import { Bot, User } from '@types'
const Application = dynamic(() => import('@components/Application'))
const DeveloperLayout = dynamic(() => import('@components/DeveloperLayout'))
const SEO = dynamic(() => import('@components/SEO'))
const Login = dynamic(() => import('@components/Login'))
const Applications: NextPage<ApplicationsProps> = ({ user }) => {
if(!user) return <Login>
<SEO title='한디리 개발자' description='한국 디스코드봇 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' />
<NextSeo title='한디리 개발자' description='한국 디스코드봇 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' />
</Login>
return <DeveloperLayout enabled='applications'>
<h1 className='text-3xl font-bold'> </h1>

View File

@ -6,7 +6,6 @@ import { BotList } from '@types'
const Hero = dynamic(() => import('@components/Hero'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const SEO = dynamic(() => import('@components/SEO'))
const BotCard = dynamic(() => import('@components/BotCard'))
const Container = dynamic(() => import('@components/Container'))
const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
@ -14,7 +13,6 @@ const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
const New:NextPage<NewProps> = ({ data }) => {
return <>
<Hero header='새로운 봇' description='최근에 한국 디스코드봇 리스트에 추가된 봇들입니다!' />
<SEO title='새로운 봇' description='최근에 추가된 봇들입니다!' />
<Container className='pb-10'>
<Advertisement />
<ResponsiveGrid>

View File

@ -11,7 +11,6 @@ import { PageCount } from '@utils/Yup'
const Hero = dynamic(() => import('@components/Hero'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const SEO = dynamic(() => import('@components/SEO'))
const BotCard = dynamic(() => import('@components/BotCard'))
const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
const Container = dynamic(() => import('@components/Container'))
@ -22,7 +21,6 @@ const Votes:NextPage<VotesProps> = ({ data }) => {
if(!data || data.data.length === 0 || data.totalPage < Number(router.query.page)) return <NotFound />
return <>
<Hero header='하트 랭킹' description='하트를 많이 받은 봇들의 순위입니다!'/>
<SEO title='하트 랭킹' description='하트를 많이 받은 봇들의 순위입니다!'/>
<section id='list'>
<Container className='pb-10'>
<Advertisement />

View File

@ -3,8 +3,9 @@ import { useState } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { NextSeo } from 'next-seo'
import { Form, Formik } from 'formik'
import { ParsedUrlQuery } from 'node:querystring'
import { ParsedUrlQuery } from 'querystring'
import { getJosaPicker } from 'josa'
import { get } from '@utils/Query'
@ -34,7 +35,6 @@ const Tag = dynamic(() => import('@components/Tag'))
const Message = dynamic(() => import('@components/Message'))
const Modal = dynamic(() => import('@components/Modal'))
const Captcha = dynamic(() => import('@components/Captcha'))
const SEO = dynamic(() => import('@components/SEO'))
const Login = dynamic(() => import('@components/Login'))
const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }) => {
@ -57,11 +57,11 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
if(!bot) return <NotFound />
if(!user) return <Login>
<SEO title='봇 정보 수정하기' description='봇의 정보를 수정합니다.'/>
<NextSeo title='봇 정보 수정하기' description='봇의 정보를 수정합니다.'/>
</Login>
if(!(bot.owners as User[]).find(el => el.id === user.id) && !checkUserFlag(user.flags, 'staff')) return <Forbidden />
return <Container paddingTop className='pt-5 pb-10'>
<SEO title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/>
<NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/>
<h1 className='text-3xl font-bold mb-8'> </h1>
<Formik initialValues={cleanObject({
agree: false,

View File

@ -1,4 +1,5 @@
import { NextPage, NextPageContext } from 'next'
import { NextSeo } from 'next-seo'
import { useState } from 'react'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
@ -10,7 +11,6 @@ import Fetch from '@utils/Fetch'
import { getToken } from '@utils/Csrf'
const Container = dynamic(() => import('@components/Container'))
const SEO = dynamic(() => import('@components/SEO'))
const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
const Button = dynamic(() => import('@components/Button'))
const BotCard = dynamic(() => import('@components/BotCard'))
@ -21,10 +21,10 @@ const Panel:NextPage<PanelProps> = ({ logged, user, submits, csrfToken }) => {
const router = useRouter()
const [ submitLimit, setSubmitLimit ] = useState(8)
if(!logged) return <Login>
<SEO title='관리 패널' />
<NextSeo title='관리 패널' />
</Login>
return <Container paddingTop className='pt-5 pb-10'>
<SEO title='관리 패널' />
<NextSeo title='관리 패널' />
<h1 className='text-4xl font-bold'> </h1>
<h2 className='text-2xl font-bold mt-4'> </h2>
<p className='text-gray-400 mb-2'> .</p>

View File

@ -1,9 +1,11 @@
import { NextPage, NextPageContext } from 'next'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { NextSeo } from 'next-seo'
import { get } from '@utils/Query'
import { git } from '@utils/Constants'
import Day from '@utils/Day'
import { SubmittedBot, User } from '@types'
@ -11,9 +13,8 @@ import useCopyClipboard from 'react-use-clipboard'
import { ParsedUrlQuery } from 'querystring'
import NotFound from 'pages/404'
import Day from '@utils/Day'
const Container = dynamic(() => import('@components/Container'))
const SEO = dynamic(() => import('@components/SEO'))
const Divider = dynamic(() => import('@components/Divider'))
const LongButton = dynamic(() => import('@components/LongButton'))
const Tag = dynamic(() => import('@components/Tag'))
@ -29,7 +30,7 @@ const PendingBot: NextPage<PendingBotProps> = ({ data }) => {
})
if(!data) return <NotFound />
return <Container paddingTop className='py-10'>
<SEO title='심사이력' />
<NextSeo title='심사이력' />
<div className='lg:flex w-full'>
<div className='w-full lg:w-3/4 lg:pr-5 py-8 text-center lg:text-left'>
{

View File

@ -9,7 +9,6 @@ import { SearchQuerySchema } from '@utils/Yup'
const Hero = dynamic(() => import('@components/Hero'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const SEO = dynamic(() => import('@components/SEO'))
const BotCard = dynamic(() => import('@components/BotCard'))
const Container = dynamic(() => import('@components/Container'))
const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
@ -20,7 +19,6 @@ const Search:NextPage<SearchProps> = ({ data, query }) => {
if(!query?.q) return <Redirect text={false} to='/' />
return <>
<Hero header={`"${query.q}" 검색 결과`} description={`'${query.q}' 에 대한 검색 결과입니다.`} />
<SEO title={`"${query.q}" 검색 결과`} description={`'${query.q}' 에 대한 검색 결과입니다.`} />
<section id='list'>
<Container>
<Advertisement />

View File

@ -15,9 +15,9 @@ import { ReportSchema } from '@utils/Yup'
import NotFound from '../404'
import { KoreanbotsEndPoints, reportCats } from '@utils/Constants'
import { NextSeo } from 'next-seo'
const Container = dynamic(() => import('@components/Container'))
const SEO = dynamic(() => import('@components/SEO'))
const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar'))
const Divider = dynamic(() => import('@components/Divider'))
const BotCard = dynamic(() => import('@components/BotCard'))
@ -37,7 +37,7 @@ const Users: NextPage<UserProps> = ({ user, data, csrfToken, theme }) => {
if (!data?.id) return <NotFound />
return (
<Container paddingTop className='py-10'>
<SEO
<NextSeo
title={data.username}
description={data.bots.length === 0 ? `${data.username}님의 프로필입니다.` : josa(
`${(data.bots as Bot[])
@ -45,7 +45,14 @@ const Users: NextPage<UserProps> = ({ user, data, csrfToken, theme }) => {
.map(el => el.name)
.join(', ')}#{} .`
)}
image={KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 })}
openGraph={{
images: [{
url: KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 }),
width: 256,
height: 256,
alt: 'User Avatar'
}]
}}
/>
<div className='lg:flex'>
<div className='w-full text-center lg:w-1/4'>

View File

@ -81,7 +81,7 @@ if (!self.define) {
});
};
}
define("./sw.js",['./workbox-6b19f60b'], function (workbox) { 'use strict';
define("./sw.js",['./workbox'], function (workbox) { 'use strict';
/**
* Welcome to your Workbox-powered service worker!

View File

@ -1 +0,0 @@
{"version":3,"file":"sw.js","sources":["../../../../private/var/folders/s2/c5ly4ybd1k92c69fqc4_50rm0000gn/T/bbfed4b2337555e56e35b242711c8b06/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/wonderlandpark/Workspace/koreanbots/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/wonderlandpark/Workspace/koreanbots/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/wonderlandpark/Workspace/koreanbots/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/wonderlandpark/Workspace/koreanbots/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \"fallback-development.js\"\n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({request, response, event, state}) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, {status: 200, statusText: 'OK', headers: response.headers}); } return response; } }, { handlerDidError: async ({request}) => self.fallback(request) }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [{ handlerDidError: async ({request}) => self.fallback(request) }] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","handlerDidError","fallback","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGgJ;EAChJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAGAA,aAAa,CACX,yBADW,CAAb;EAUAC,IAAI,CAACC,WAAL;AAEAC,sBAAyB;AAIzBC,uBAA6B,CAAC,GAAD,EAAM,IAAIC,oBAAJ,CAAoC;EAAE,eAAY,WAAd;EAA2BC,EAAAA,OAAO,EAAE,CAAC;EAAEC,IAAAA,eAAe,EAAE,OAAO;EAACC,MAAAA,OAAD;EAAUC,MAAAA,QAAV;EAAoBC,MAAAA,KAApB;EAA2BC,MAAAA;EAA3B,KAAP,KAA6C;EAAE,UAAIF,QAAQ,IAAIA,QAAQ,CAACG,IAAT,KAAkB,gBAAlC,EAAoD;EAAE,eAAO,IAAIC,QAAJ,CAAaJ,QAAQ,CAACK,IAAtB,EAA4B;EAACC,UAAAA,MAAM,EAAE,GAAT;EAAcC,UAAAA,UAAU,EAAE,IAA1B;EAAgCC,UAAAA,OAAO,EAAER,QAAQ,CAACQ;EAAlD,SAA5B,CAAP;EAAiG;;EAAC,aAAOR,QAAP;EAAkB;EAA5O,GAAD,EAAiP;EAAES,IAAAA,eAAe,EAAE,OAAO;EAACV,MAAAA;EAAD,KAAP,KAAqBP,IAAI,CAACkB,QAAL,CAAcX,OAAd;EAAxC,GAAjP;EAApC,CAApC,CAAN,EAAqY,KAArY,CAA7B;AACAJ,uBAA6B,CAAC,KAAD,EAAQ,IAAIgB,mBAAJ,CAAmC;EAAE,eAAY,KAAd;EAAqBd,EAAAA,OAAO,EAAE,CAAC;EAAEY,IAAAA,eAAe,EAAE,OAAO;EAACV,MAAAA;EAAD,KAAP,KAAqBP,IAAI,CAACkB,QAAL,CAAcX,OAAd;EAAxC,GAAD;EAA9B,CAAnC,CAAR,EAAgJ,KAAhJ,CAA7B;;"}

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
define("./workbox-6b19f60b.js",['exports'], function (exports) { 'use strict';
define("./workbox.js",['exports'], function (exports) { 'use strict';
try {
self['workbox:core:6.1.5'] && _();
@ -2698,4 +2698,3 @@ define("./workbox-6b19f60b.js",['exports'], function (exports) { 'use strict';
exports.registerRoute = registerRoute;
});
//# sourceMappingURL=workbox-6b19f60b.js.map

View File

@ -190,10 +190,11 @@ export const DiscordEnpoints = {
export const KoreanbotsEndPoints = {
OG: class {
static root = 'https://og.kbots.link'
static origin = 'https://beta.koreanbots.dev'
static bot(id: string, name: string, bio: string, tags: string[], stats: string[]) {
const u = new URL(this.root)
u.pathname = name
u.searchParams.append('image', KoreanbotsEndPoints.CDN.avatar(id, { format: 'webp', size: 256 }))
u.searchParams.append('image', this.origin + KoreanbotsEndPoints.CDN.avatar(id, { format: 'webp', size: 256 }))
u.searchParams.append('bio', bio)
tags.map(t => u.searchParams.append('tags', t))
stats.map(s => u.searchParams.append('stats', s))

View File

@ -6579,6 +6579,11 @@ next-pwa@5.2.21:
workbox-webpack-plugin "^6.1.5"
workbox-window "^6.1.5"
next-seo@4.24.0:
version "4.24.0"
resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-4.24.0.tgz#7c0447da00b8574dcda5c6979771a7f6efd24f55"
integrity sha512-9VQXfXAelhE+hAWzJ4azigQaW3FPX0kU0eYKFQXKsQjgY7AWtukjRGXls0oSIk8khhDJwmCt46EwsO9n5DDW6Q==
next-session@3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/next-session/-/next-session-3.4.0.tgz#799757624a336b752298e9771ffc1d7282d76e91"