diff --git a/src/app/(routes)/login/page.tsx b/src/app/(routes)/login/page.tsx
index d4aa356f..98231396 100644
--- a/src/app/(routes)/login/page.tsx
+++ b/src/app/(routes)/login/page.tsx
@@ -1,4 +1,9 @@
import Login from '@/components/Login/Login'
+import { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: '로그인',
+}
const LoginPage = () => {
return (
diff --git a/src/app/(routes)/notification/layout.tsx b/src/app/(routes)/notification/layout.tsx
index 2d312b22..777340cd 100644
--- a/src/app/(routes)/notification/layout.tsx
+++ b/src/app/(routes)/notification/layout.tsx
@@ -1,27 +1,12 @@
-'use client'
+import { NotificationController } from '@/components'
+import { Metadata } from 'next'
-import Tab from '@/components/common/Tab/Tab'
-import TabItem from '@/components/common/Tab/TabItem'
-import useTab from '@/components/common/Tab/hooks/useTab'
+export const metadata: Metadata = {
+ title: '알림',
+}
const NotificationLayout = ({ children }: { children: React.ReactNode }) => {
- const { currentTab, tabList } = useTab({ type: 'notification' })
-
- return (
- <>
-
- {tabList.map((tabItem) => (
-
- ))}
-
-
{children}
- >
- )
+ return {children}
}
export default NotificationLayout
diff --git a/src/app/(routes)/register/page.tsx b/src/app/(routes)/register/page.tsx
index f3ac0308..02c6d36e 100644
--- a/src/app/(routes)/register/page.tsx
+++ b/src/app/(routes)/register/page.tsx
@@ -1,4 +1,9 @@
import UserInfoForm from '@/components/UserInfoForm/UserInfoForm'
+import { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: '회원가입',
+}
const RegisterPage = () => {
return (
diff --git a/src/app/(routes)/search/page.tsx b/src/app/(routes)/search/page.tsx
index 28b8336e..b3637c71 100644
--- a/src/app/(routes)/search/page.tsx
+++ b/src/app/(routes)/search/page.tsx
@@ -1,72 +1,24 @@
-'use client'
+import { SearchController } from '@/components'
+import { Metadata } from 'next'
-import { CategoryList, Dropdown, SpaceList } from '@/components'
-import UserList from '@/components/UserList/UserList'
-import { useCategoryParam, useSortParam } from '@/hooks'
-import { fetchSearchSpaces } from '@/services/space/spaces'
-import { fetchSearchUsers } from '@/services/user/search/search'
-import { cls } from '@/utils'
-import { useSearchParams } from 'next/navigation'
+type SearchPageProps = {
+ searchParams: { [key: string]: string | string[] | undefined }
+}
-const SearchPage = () => {
- const searchParams = useSearchParams()
- const keyword = searchParams.get('keyword')
- const target = searchParams.get('target')
- const { sort, sortIndex, handleSortChange } = useSortParam('space')
- const { category, categoryIndex, handleCategoryChange } =
- useCategoryParam('all')
+export async function generateMetadata({
+ searchParams,
+}: SearchPageProps): Promise {
+ const { target, keyword } = searchParams
- return (
- <>
-
-
-
- '{keyword}' 에 대한{' '}
- {target === 'space' ? '스페이스' : target === 'user' ? '유저' : ''}{' '}
- 검색 결과
-
- {target === 'space' && (
-
-
-
- )}
-
- {target === 'space' && (
-
- )}
-
-
- {target === 'space' && (
-
- )}
- {target === 'user' && (
-
- )}
-
- >
- )
+ return {
+ title: `'${keyword ?? ''}' ${
+ target === 'space' ? '스페이스' : target === 'user' ? '유저' : ''
+ } 검색 결과`,
+ }
+}
+
+const SearchPage = () => {
+ return
}
export default SearchPage
diff --git a/src/app/(routes)/space/[spaceId]/layout.tsx b/src/app/(routes)/space/[spaceId]/layout.tsx
new file mode 100644
index 00000000..d3a2d748
--- /dev/null
+++ b/src/app/(routes)/space/[spaceId]/layout.tsx
@@ -0,0 +1,29 @@
+import { fetchGetSpace } from '@/services/space/space'
+import { Metadata } from 'next'
+
+type SpaceLayoutProps = {
+ params: { spaceId: number }
+}
+
+export async function generateMetadata({
+ params,
+}: SpaceLayoutProps): Promise {
+ const spaceId = params.spaceId
+ const space = await fetchGetSpace({ spaceId })
+
+ return {
+ title: space.spaceName,
+ description: space.description,
+ openGraph: {
+ title: `${space.spaceName} • LinkHub`,
+ description: space.description,
+ images: space.spaceImagePath,
+ },
+ }
+}
+
+const layout = ({ children }: { children: React.ReactNode }) => {
+ return children
+}
+
+export default layout
diff --git a/src/app/(routes)/space/create/page.tsx b/src/app/(routes)/space/create/page.tsx
index 79cc4dda..6b8c41e3 100644
--- a/src/app/(routes)/space/create/page.tsx
+++ b/src/app/(routes)/space/create/page.tsx
@@ -1,4 +1,9 @@
import SpaceForm from '@/components/Space/SpaceForm'
+import { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: '스페이스 생성',
+}
const SpaceCreatePage = () => {
return (
diff --git a/src/app/(routes)/user/[userId]/layout.tsx b/src/app/(routes)/user/[userId]/layout.tsx
index 7228c3a1..d2857576 100644
--- a/src/app/(routes)/user/[userId]/layout.tsx
+++ b/src/app/(routes)/user/[userId]/layout.tsx
@@ -1,37 +1,25 @@
-'use client'
+import { UserController } from '@/components'
+import { fetchGetUserProfile } from '@/services/user/profile/profile'
+import { Metadata } from 'next'
-import React from 'react'
-import { Spinner } from '@/components'
-import Tab from '@/components/common/Tab/Tab'
-import TabItem from '@/components/common/Tab/TabItem'
-import useTab from '@/components/common/Tab/hooks/useTab'
-import useGetProfile from '@/hooks/useGetProfile'
+type Props = {
+ params: { userId: number }
+}
-const UserLayout = ({ children }: { children: React.ReactNode }) => {
- const { user, myId, isProfileLoading } = useGetProfile()
- const { currentTab, tabList } = useTab({
- type: 'user',
- userId: user?.memberId,
- myId,
- })
+export async function generateMetadata({ params }: Props): Promise {
+ const userId = params.userId
+ const user = await fetchGetUserProfile({ memberId: userId })
- return (
- <>
- {!isProfileLoading && (
-
- {tabList.map((tabItem) => (
-
- ))}
-
- )}
- {children}
- >
- )
+ return {
+ title: user.nickname,
+ openGraph: {
+ title: `${user.nickname} • LinkHub`,
+ },
+ }
+}
+
+const UserLayout = ({ children }: { children: React.ReactNode }) => {
+ return {children}
}
export default UserLayout
diff --git a/src/app/(routes)/user/setting/page.tsx b/src/app/(routes)/user/setting/page.tsx
index b4d02cd9..79b7d383 100644
--- a/src/app/(routes)/user/setting/page.tsx
+++ b/src/app/(routes)/user/setting/page.tsx
@@ -1,21 +1,12 @@
-'use client'
+import { SettingController } from '@/components'
+import { Metadata } from 'next'
-import UserInfoForm from '@/components/UserInfoForm/UserInfoForm'
-import { useCurrentUser } from '@/hooks/useCurrentUser'
+export const metadata: Metadata = {
+ title: '프로필 수정',
+}
const UserSettingPage = () => {
- const { currentUser } = useCurrentUser()
-
- return (
-
- {currentUser && (
-
- )}
-
- )
+ return
}
export default UserSettingPage
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
index 718d6fea..700688c9 100644
Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 0fcfd667..7b7e3572 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -7,8 +7,19 @@ import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
+ title: {
+ template: '%s • LinkHub',
+ default: 'LinkHub',
+ },
+ description: '링크 아카이빙 및 공유 플랫폼',
+ openGraph: {
+ title: 'LinkHub',
+ description: '링크 아카이빙 및 공유 플랫폼',
+ url: 'https://link-hub.site',
+ siteName: 'LinkHub',
+ locale: 'ko_KR',
+ type: 'website',
+ },
}
export default function RootLayout({
@@ -21,7 +32,7 @@ export default function RootLayout({
diff --git a/src/app/opengraph-image.png b/src/app/opengraph-image.png
new file mode 100644
index 00000000..29860e7e
Binary files /dev/null and b/src/app/opengraph-image.png differ
diff --git a/src/app/twitter-image.png b/src/app/twitter-image.png
new file mode 100644
index 00000000..29860e7e
Binary files /dev/null and b/src/app/twitter-image.png differ
diff --git a/src/components/NotificationController/NotificationController.tsx b/src/components/NotificationController/NotificationController.tsx
new file mode 100644
index 00000000..2d312b22
--- /dev/null
+++ b/src/components/NotificationController/NotificationController.tsx
@@ -0,0 +1,27 @@
+'use client'
+
+import Tab from '@/components/common/Tab/Tab'
+import TabItem from '@/components/common/Tab/TabItem'
+import useTab from '@/components/common/Tab/hooks/useTab'
+
+const NotificationLayout = ({ children }: { children: React.ReactNode }) => {
+ const { currentTab, tabList } = useTab({ type: 'notification' })
+
+ return (
+ <>
+
+ {tabList.map((tabItem) => (
+
+ ))}
+
+ {children}
+ >
+ )
+}
+
+export default NotificationLayout
diff --git a/src/components/SearchController/SearchController.tsx b/src/components/SearchController/SearchController.tsx
new file mode 100644
index 00000000..0f451bb9
--- /dev/null
+++ b/src/components/SearchController/SearchController.tsx
@@ -0,0 +1,72 @@
+'use client'
+
+import { CategoryList, Dropdown, SpaceList } from '@/components'
+import UserList from '@/components/UserList/UserList'
+import { useCategoryParam, useSortParam } from '@/hooks'
+import { fetchSearchSpaces } from '@/services/space/spaces'
+import { fetchSearchUsers } from '@/services/user/search/search'
+import { cls } from '@/utils'
+import { useSearchParams } from 'next/navigation'
+
+const SearchController = () => {
+ const searchParams = useSearchParams()
+ const keyword = searchParams.get('keyword')
+ const target = searchParams.get('target')
+ const { sort, sortIndex, handleSortChange } = useSortParam('space')
+ const { category, categoryIndex, handleCategoryChange } =
+ useCategoryParam('all')
+
+ return (
+ <>
+
+
+
+ '{keyword}' 에 대한{' '}
+ {target === 'space' ? '스페이스' : target === 'user' ? '유저' : ''}{' '}
+ 검색 결과
+
+ {target === 'space' && (
+
+
+
+ )}
+
+ {target === 'space' && (
+
+ )}
+
+
+ {target === 'space' && (
+
+ )}
+ {target === 'user' && (
+
+ )}
+
+ >
+ )
+}
+
+export default SearchController
diff --git a/src/components/SettingController/SettingController.tsx b/src/components/SettingController/SettingController.tsx
new file mode 100644
index 00000000..c6f17566
--- /dev/null
+++ b/src/components/SettingController/SettingController.tsx
@@ -0,0 +1,21 @@
+'use client'
+
+import UserInfoForm from '@/components/UserInfoForm/UserInfoForm'
+import { useCurrentUser } from '@/hooks/useCurrentUser'
+
+const SettingController = () => {
+ const { currentUser } = useCurrentUser()
+
+ return (
+
+ {currentUser && (
+
+ )}
+
+ )
+}
+
+export default SettingController
diff --git a/src/components/UserController/UserController.tsx b/src/components/UserController/UserController.tsx
new file mode 100644
index 00000000..deea6e5b
--- /dev/null
+++ b/src/components/UserController/UserController.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+import React from 'react'
+import Tab from '@/components/common/Tab/Tab'
+import TabItem from '@/components/common/Tab/TabItem'
+import useTab from '@/components/common/Tab/hooks/useTab'
+import useGetProfile from '@/hooks/useGetProfile'
+
+const UserController = ({ children }: { children: React.ReactNode }) => {
+ const { user, myId, isProfileLoading } = useGetProfile()
+ const { currentTab, tabList } = useTab({
+ type: 'user',
+ userId: user?.memberId,
+ myId,
+ })
+
+ return (
+ <>
+ {!isProfileLoading && (
+
+ {tabList.map((tabItem) => (
+
+ ))}
+
+ )}
+ {children}
+ >
+ )
+}
+
+export default UserController
diff --git a/src/components/index.ts b/src/components/index.ts
index cf19a483..7664fcb1 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -19,3 +19,7 @@ export { default as Comment } from './common/Comment/Comment'
export { default as Notification } from './common/Notification/Notification'
export { default as SpaceList } from './SpaceList/SpaceList'
export { default as Spinner } from './common/Spinner/Spinner'
+export { default as SearchController } from './SearchController/SearchController'
+export { default as NotificationController } from './NotificationController/NotificationController'
+export { default as UserController } from './UserController/UserController'
+export { default as SettingController } from './SettingController/SettingController'