From 860e8bb5d84c02276dba7631b30fcf06b434e98a Mon Sep 17 00:00:00 2001
From: taiy <53635909+taiyme@users.noreply.github.com>
Date: Fri, 16 Feb 2024 16:17:09 +0900
Subject: [PATCH] =?UTF-8?q?fix(frontend/pageMetadata):=20=E3=83=9A?=
 =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB=E3=81=8C?=
 =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F?=
 =?UTF-8?q?=E9=A1=8C=20(#13289)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
---
 .../frontend/src/components/MkPageWindow.vue  | 16 +++--
 .../src/components/global/MkPageHeader.vue    | 20 +++---
 packages/frontend/src/config.ts               |  2 +-
 packages/frontend/src/pages/_error_.vue       |  4 +-
 packages/frontend/src/pages/about-misskey.vue |  4 +-
 packages/frontend/src/pages/about.vue         |  4 +-
 packages/frontend/src/pages/achievements.vue  |  4 +-
 packages/frontend/src/pages/admin-file.vue    |  6 +-
 packages/frontend/src/pages/admin-user.vue    |  4 +-
 .../frontend/src/pages/admin/_header_.vue     | 12 ++--
 packages/frontend/src/pages/admin/abuses.vue  |  4 +-
 packages/frontend/src/pages/admin/ads.vue     |  4 +-
 .../src/pages/admin/announcements.vue         |  4 +-
 .../frontend/src/pages/admin/branding.vue     |  4 +-
 .../frontend/src/pages/admin/database.vue     |  4 +-
 .../src/pages/admin/email-settings.vue        |  4 +-
 .../src/pages/admin/external-services.vue     |  4 +-
 .../frontend/src/pages/admin/federation.vue   |  4 +-
 packages/frontend/src/pages/admin/files.vue   |  4 +-
 packages/frontend/src/pages/admin/index.vue   | 14 ++--
 .../src/pages/admin/instance-block.vue        |  4 +-
 packages/frontend/src/pages/admin/invites.vue |  4 +-
 .../frontend/src/pages/admin/moderation.vue   |  4 +-
 packages/frontend/src/pages/admin/modlog.vue  |  4 +-
 .../src/pages/admin/object-storage.vue        |  4 +-
 .../src/pages/admin/other-settings.vue        |  4 +-
 .../frontend/src/pages/admin/overview.vue     |  4 +-
 .../src/pages/admin/proxy-account.vue         |  4 +-
 packages/frontend/src/pages/admin/queue.vue   |  4 +-
 packages/frontend/src/pages/admin/relays.vue  |  4 +-
 .../frontend/src/pages/admin/roles.edit.vue   |  7 +-
 .../frontend/src/pages/admin/roles.role.vue   |  6 +-
 packages/frontend/src/pages/admin/roles.vue   |  4 +-
 .../frontend/src/pages/admin/security.vue     |  4 +-
 .../frontend/src/pages/admin/server-rules.vue |  4 +-
 .../frontend/src/pages/admin/settings.vue     |  4 +-
 packages/frontend/src/pages/admin/users.vue   |  4 +-
 packages/frontend/src/pages/ads.vue           |  4 +-
 packages/frontend/src/pages/announcements.vue |  4 +-
 .../frontend/src/pages/antenna-timeline.vue   |  6 +-
 packages/frontend/src/pages/api-console.vue   |  4 +-
 packages/frontend/src/pages/auth.vue          |  4 +-
 .../frontend/src/pages/avatar-decorations.vue |  4 +-
 .../frontend/src/pages/channel-editor.vue     |  7 +-
 packages/frontend/src/pages/channel.vue       |  6 +-
 packages/frontend/src/pages/channels.vue      |  4 +-
 packages/frontend/src/pages/clicker.vue       |  4 +-
 packages/frontend/src/pages/clip.vue          |  6 +-
 .../src/pages/custom-emojis-manager.vue       |  4 +-
 packages/frontend/src/pages/drive.file.vue    |  4 +-
 packages/frontend/src/pages/drive.vue         |  4 +-
 .../src/pages/drop-and-fusion.game.vue        |  4 +-
 .../frontend/src/pages/drop-and-fusion.vue    |  4 +-
 packages/frontend/src/pages/explore.vue       |  4 +-
 packages/frontend/src/pages/favorites.vue     |  4 +-
 .../frontend/src/pages/flash/flash-edit.vue   |  6 +-
 .../frontend/src/pages/flash/flash-index.vue  |  4 +-
 packages/frontend/src/pages/flash/flash.vue   | 20 +++---
 .../frontend/src/pages/follow-requests.vue    |  4 +-
 packages/frontend/src/pages/gallery/edit.vue  |  7 +-
 packages/frontend/src/pages/gallery/index.vue |  4 +-
 packages/frontend/src/pages/gallery/post.vue  | 10 +--
 packages/frontend/src/pages/games.vue         |  4 +-
 .../frontend/src/pages/install-extensions.vue |  4 +-
 packages/frontend/src/pages/instance-info.vue |  4 +-
 packages/frontend/src/pages/invite.vue        |  4 +-
 packages/frontend/src/pages/list.vue          |  6 +-
 packages/frontend/src/pages/miauth.vue        |  4 +-
 .../frontend/src/pages/my-antennas/create.vue |  4 +-
 .../frontend/src/pages/my-antennas/edit.vue   |  4 +-
 .../frontend/src/pages/my-antennas/index.vue  |  4 +-
 .../frontend/src/pages/my-clips/index.vue     |  8 +--
 .../frontend/src/pages/my-lists/index.vue     |  4 +-
 packages/frontend/src/pages/my-lists/list.vue |  6 +-
 packages/frontend/src/pages/not-found.vue     |  4 +-
 packages/frontend/src/pages/note.vue          | 20 +++---
 packages/frontend/src/pages/notifications.vue |  4 +-
 packages/frontend/src/pages/oauth.vue         |  4 +-
 .../src/pages/page-editor/page-editor.vue     | 16 ++---
 packages/frontend/src/pages/page.vue          | 20 +++---
 packages/frontend/src/pages/pages.vue         |  4 +-
 packages/frontend/src/pages/registry.keys.vue |  4 +-
 .../frontend/src/pages/registry.value.vue     |  4 +-
 packages/frontend/src/pages/registry.vue      |  4 +-
 .../frontend/src/pages/reset-password.vue     |  4 +-
 packages/frontend/src/pages/reversi/game.vue  |  4 +-
 packages/frontend/src/pages/reversi/index.vue |  6 +-
 packages/frontend/src/pages/role.vue          |  6 +-
 packages/frontend/src/pages/scratchpad.vue    |  4 +-
 packages/frontend/src/pages/search.vue        |  4 +-
 .../frontend/src/pages/settings/accounts.vue  |  4 +-
 packages/frontend/src/pages/settings/api.vue  |  4 +-
 packages/frontend/src/pages/settings/apps.vue |  4 +-
 .../src/pages/settings/avatar-decoration.vue  |  4 +-
 .../src/pages/settings/custom-css.vue         |  4 +-
 packages/frontend/src/pages/settings/deck.vue |  4 +-
 .../src/pages/settings/drive-cleaner.vue      |  4 +-
 .../frontend/src/pages/settings/drive.vue     |  4 +-
 .../frontend/src/pages/settings/email.vue     |  4 +-
 .../src/pages/settings/emoji-picker.vue       |  4 +-
 .../frontend/src/pages/settings/general.vue   |  4 +-
 .../src/pages/settings/import-export.vue      |  4 +-
 .../frontend/src/pages/settings/index.vue     | 14 ++--
 .../frontend/src/pages/settings/migration.vue |  4 +-
 .../src/pages/settings/mute-block.vue         |  4 +-
 .../frontend/src/pages/settings/navbar.vue    |  4 +-
 .../src/pages/settings/notifications.vue      |  4 +-
 .../frontend/src/pages/settings/other.vue     |  4 +-
 .../src/pages/settings/plugin.install.vue     |  4 +-
 .../frontend/src/pages/settings/plugin.vue    |  4 +-
 .../pages/settings/preferences-backups.vue    |  6 +-
 .../frontend/src/pages/settings/privacy.vue   |  4 +-
 .../frontend/src/pages/settings/profile.vue   |  4 +-
 .../frontend/src/pages/settings/roles.vue     |  4 +-
 .../frontend/src/pages/settings/security.vue  |  4 +-
 .../frontend/src/pages/settings/sounds.vue    |  4 +-
 .../frontend/src/pages/settings/statusbar.vue |  4 +-
 .../src/pages/settings/theme.install.vue      |  4 +-
 .../src/pages/settings/theme.manage.vue       |  4 +-
 .../frontend/src/pages/settings/theme.vue     |  4 +-
 .../src/pages/settings/webhook.edit.vue       |  4 +-
 .../src/pages/settings/webhook.new.vue        |  4 +-
 .../frontend/src/pages/settings/webhook.vue   |  4 +-
 packages/frontend/src/pages/share.vue         |  4 +-
 packages/frontend/src/pages/tag.vue           |  4 +-
 packages/frontend/src/pages/theme-editor.vue  |  4 +-
 packages/frontend/src/pages/timeline.vue      |  4 +-
 .../frontend/src/pages/user-list-timeline.vue |  6 +-
 packages/frontend/src/pages/user-tag.vue      |  4 +-
 .../frontend/src/pages/user/followers.vue     | 15 ++--
 .../frontend/src/pages/user/following.vue     | 15 ++--
 packages/frontend/src/pages/user/index.vue    | 23 +++---
 packages/frontend/src/pages/welcome.vue       |  4 +-
 .../frontend/src/scripts/page-metadata.ts     | 72 ++++++++++++-------
 packages/frontend/src/ui/classic.vue          | 18 +++--
 packages/frontend/src/ui/deck/main-column.vue | 16 +++--
 packages/frontend/src/ui/minimum.vue          | 20 ++++--
 packages/frontend/src/ui/universal.vue        | 20 ++++--
 packages/frontend/src/ui/visitor.vue          | 30 ++++----
 packages/frontend/src/ui/zen.vue              | 20 ++++--
 140 files changed, 496 insertions(+), 430 deletions(-)

diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 338403df14..aa4509b14b 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -16,9 +16,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 	@closed="$emit('closed')"
 >
 	<template #header>
-		<template v-if="pageMetadata?.value">
-			<i v-if="pageMetadata.value.icon" :class="pageMetadata.value.icon" style="margin-right: 0.5em;"></i>
-			<span>{{ pageMetadata.value.title }}</span>
+		<template v-if="pageMetadata">
+			<i v-if="pageMetadata.icon" :class="pageMetadata.icon" style="margin-right: 0.5em;"></i>
+			<span>{{ pageMetadata.title }}</span>
 		</template>
 	</template>
 
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, ComputedRef, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
+import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
 import RouterView from '@/components/global/RouterView.vue';
 import MkWindow from '@/components/MkWindow.vue';
 import { popout as _popout } from '@/scripts/popout.js';
@@ -37,7 +37,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { url } from '@/config.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { i18n } from '@/i18n.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { openingWindowsCount } from '@/os.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
@@ -56,7 +56,7 @@ const routerFactory = useRouterFactory();
 const windowRouter = routerFactory(props.initialPath);
 
 const contents = shallowRef<HTMLElement | null>(null);
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | PageMetadata>(null);
 const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
 const history = ref<{ path: string; key: any; }[]>([{
 	path: windowRouter.getCurrentPath(),
@@ -101,9 +101,11 @@ windowRouter.addListener('replace', ctx => {
 windowRouter.init();
 
 provide('router', windowRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
 });
+provideReactiveMetadata(pageMetadata);
 provide('shouldOmitHeaderTitle', true);
 provide('shouldHeaderThin', true);
 provide('forceSpacerMin', true);
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 8d0374a20a..f16d951679 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -11,18 +11,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 		<div v-else-if="!thin_ && narrow && !hideTitle" :class="$style.buttonsLeft"/>
 
-		<template v-if="metadata">
+		<template v-if="pageMetadata">
 			<div v-if="!hideTitle" :class="$style.titleContainer" @click="top">
-				<div v-if="metadata.avatar" :class="$style.titleAvatarContainer">
-					<MkAvatar :class="$style.titleAvatar" :user="metadata.avatar" indicator/>
+				<div v-if="pageMetadata.avatar" :class="$style.titleAvatarContainer">
+					<MkAvatar :class="$style.titleAvatar" :user="pageMetadata.avatar" indicator/>
 				</div>
-				<i v-else-if="metadata.icon" :class="[$style.titleIcon, metadata.icon]"></i>
+				<i v-else-if="pageMetadata.icon" :class="[$style.titleIcon, pageMetadata.icon]"></i>
 
 				<div :class="$style.title">
-					<MkUserName v-if="metadata.userName" :user="metadata.userName" :nowrap="true"/>
-					<div v-else-if="metadata.title">{{ metadata.title }}</div>
-					<div v-if="metadata.subtitle" :class="$style.subtitle">
-						{{ metadata.subtitle }}
+					<MkUserName v-if="pageMetadata.userName" :user="pageMetadata.userName" :nowrap="true"/>
+					<div v-else-if="pageMetadata.title">{{ pageMetadata.title }}</div>
+					<div v-if="pageMetadata.subtitle" :class="$style.subtitle">
+						{{ pageMetadata.subtitle }}
 					</div>
 				</div>
 			</div>
@@ -46,7 +46,7 @@ import tinycolor from 'tinycolor2';
 import XTabs, { Tab } from './MkPageHeader.tabs.vue';
 import { scrollToTop } from '@/scripts/scroll.js';
 import { globalEvents } from '@/events.js';
-import { injectPageMetadata } from '@/scripts/page-metadata.js';
+import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
 import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
 import { PageHeaderItem } from '@/types/page-header.js';
 
@@ -64,7 +64,7 @@ const emit = defineEmits<{
 	(ev: 'update:tab', key: string);
 }>();
 
-const metadata = injectPageMetadata();
+const pageMetadata = injectReactiveMetadata();
 
 const hideTitle = inject('shouldOmitHeaderTitle', false);
 const thin_ = props.thin || inject('shouldHeaderThin', false);
diff --git a/packages/frontend/src/config.ts b/packages/frontend/src/config.ts
index 113d52d0a8..277dfc12aa 100644
--- a/packages/frontend/src/config.ts
+++ b/packages/frontend/src/config.ts
@@ -18,7 +18,7 @@ export const langs = _LANGS_;
 const preParseLocale = miLocalStorage.getItem('locale');
 export let locale = preParseLocale ? JSON.parse(preParseLocale) : null;
 export const version = _VERSION_;
-export const instanceName = siteName === 'Misskey' ? host : siteName;
+export const instanceName = siteName === 'Misskey' || siteName == null ? host : siteName;
 export const ui = miLocalStorage.getItem('ui');
 export const debug = miLocalStorage.getItem('debug') === 'true';
 
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index c5673cbaa0..c04f399c6d 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -67,10 +67,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.error,
 	icon: 'ti ti-alert-triangle',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 5be1634e05..a2ecae3b80 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -363,10 +363,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.aboutMisskey,
 	icon: null,
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index f5744bab00..f99eb9c577 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -164,10 +164,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-chart-line',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.instanceInfo,
 	icon: 'ti ti-info-circle',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue
index 74f11f74a7..77ab473ea2 100644
--- a/packages/frontend/src/pages/achievements.vue
+++ b/packages/frontend/src/pages/achievements.vue
@@ -48,10 +48,10 @@ onDeactivated(() => {
 	}
 });
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.achievements,
 	icon: 'ti ti-medal',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index 9a57225dab..d8311186ab 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -140,10 +140,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-code',
 }]);
 
-definePageMetadata(computed(() => ({
-	title: file.value ? i18n.ts.file + ': ' + file.value.name : i18n.ts.file,
+definePageMetadata(() => ({
+	title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
 	icon: 'ti ti-file',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index 83f1ed2773..2cef55df6c 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -518,10 +518,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-code',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: user.value ? acct(user.value) : i18n.ts.userInfo,
 	icon: 'ti ti-user-exclamation',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index 9c05329499..c5a9609e6e 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div ref="el" class="fdidabkc" :style="{ background: bg }" @click="onClick">
-	<template v-if="metadata">
+	<template v-if="pageMetadata">
 		<div class="titleContainer" @click="showTabsPopup">
-			<i v-if="metadata.icon" class="icon" :class="metadata.icon"></i>
+			<i v-if="pageMetadata.icon" class="icon" :class="pageMetadata.icon"></i>
 
 			<div class="title">
-				<div class="title">{{ metadata.title }}</div>
+				<div class="title">{{ pageMetadata.title }}</div>
 			</div>
 		</div>
 		<div class="tabs">
@@ -39,7 +39,7 @@ import { popupMenu } from '@/os.js';
 import { scrollToTop } from '@/scripts/scroll.js';
 import MkButton from '@/components/MkButton.vue';
 import { globalEvents } from '@/events.js';
-import { injectPageMetadata } from '@/scripts/page-metadata.js';
+import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
 
 type Tab = {
 	key?: string | null;
@@ -65,7 +65,7 @@ const emit = defineEmits<{
 	(ev: 'update:tab', key: string);
 }>();
 
-const metadata = injectPageMetadata();
+const pageMetadata = injectReactiveMetadata();
 
 const el = shallowRef<HTMLElement>(null);
 const tabRefs = {};
@@ -118,7 +118,7 @@ function onTabClick(tab: Tab, ev: MouseEvent): void {
 }
 
 const calcBg = () => {
-	const rawBg = metadata?.bg ?? 'var(--bg)';
+	const rawBg = pageMetadata.value?.bg ?? 'var(--bg)';
 	const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
 	tinyBg.setAlpha(0.85);
 	bg.value = tinyBg.toRgbString();
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index f16e9d22f7..d2f4a4b531 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -87,8 +87,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.abuseReports,
 	icon: 'ti ti-exclamation-circle',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index 72ae14b3e9..bd442ccc69 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -255,10 +255,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.ads,
 	icon: 'ti ti-ad',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index 300af01640..e7fb62ec1d 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -157,8 +157,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.announcements,
 	icon: 'ti ti-speakerphone',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 8fecdca5b8..4ac2011aaf 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -159,10 +159,10 @@ function save() {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.branding,
 	icon: 'ti ti-paint',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/database.vue b/packages/frontend/src/pages/admin/database.vue
index e7c90f5a03..e092efd92c 100644
--- a/packages/frontend/src/pages/admin/database.vue
+++ b/packages/frontend/src/pages/admin/database.vue
@@ -33,8 +33,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.database,
 	icon: 'ti ti-database',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index c36ea81a01..839b9bee16 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -130,10 +130,10 @@ function save() {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.emailServer,
 	icon: 'ti ti-mail',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index 2c9d447c4d..ba3eb05e72 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -69,10 +69,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.externalServices,
 	icon: 'ti ti-link',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue
index 2dd1a968bb..de27e1f67a 100644
--- a/packages/frontend/src/pages/admin/federation.vue
+++ b/packages/frontend/src/pages/admin/federation.vue
@@ -102,10 +102,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.federation,
 	icon: 'ti ti-whirl',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue
index eadfe63fa9..3fe021e771 100644
--- a/packages/frontend/src/pages/admin/files.vue
+++ b/packages/frontend/src/pages/admin/files.vue
@@ -108,8 +108,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.files,
 	icon: 'ti ti-cloud',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index d83e14018c..d4a41c66cc 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, Ref, onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
+import { onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
 import MkSuperMenu from '@/components/MkSuperMenu.vue';
 import MkInfo from '@/components/MkInfo.vue';
@@ -36,7 +36,7 @@ import { instance } from '@/instance.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { useRouter } from '@/router/supplier.js';
 
 const isEmpty = (x: string | null) => x == null || x === '';
@@ -52,7 +52,7 @@ const indexInfo = {
 provide('shouldOmitHeaderTitle', false);
 
 const INFO = ref(indexInfo);
-const childInfo: Ref<ComputedRef<PageMetadata> | null> = ref(null);
+const childInfo = ref<null | PageMetadata>(null);
 const narrow = ref(false);
 const view = ref(null);
 const el = ref<HTMLDivElement | null>(null);
@@ -257,14 +257,16 @@ watch(router.currentRef, (to) => {
 	}
 });
 
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	if (info == null) {
 		childInfo.value = null;
 	} else {
 		childInfo.value = info;
-		INFO.value.needWideArea = info.value.needWideArea ?? undefined;
+		INFO.value.needWideArea = info.needWideArea ?? undefined;
 	}
 });
+provideReactiveMetadata(INFO);
 
 function invite() {
 	misskeyApi('admin/invite/create').then(x => {
@@ -318,7 +320,7 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(INFO.value);
+definePageMetadata(() => INFO.value);
 
 defineExpose({
 	header: {
diff --git a/packages/frontend/src/pages/admin/instance-block.vue b/packages/frontend/src/pages/admin/instance-block.vue
index 7cba6823e7..5167b2e6b2 100644
--- a/packages/frontend/src/pages/admin/instance-block.vue
+++ b/packages/frontend/src/pages/admin/instance-block.vue
@@ -66,8 +66,8 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-eye-off',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.instanceBlocking,
 	icon: 'ti ti-ban',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue
index a2187fbb27..95727fb14c 100644
--- a/packages/frontend/src/pages/admin/invites.vue
+++ b/packages/frontend/src/pages/admin/invites.vue
@@ -113,10 +113,10 @@ function deleted(id: string) {
 const headerActions = computed(() => []);
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.invite,
 	icon: 'ti ti-user-plus',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index ae0606ed0d..d6cb1e39a7 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -116,10 +116,10 @@ function save() {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.moderation,
 	icon: 'ti ti-shield',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue
index 7cfd530790..5e251b8a6f 100644
--- a/packages/frontend/src/pages/admin/modlog.vue
+++ b/packages/frontend/src/pages/admin/modlog.vue
@@ -60,8 +60,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.moderationLogs,
 	icon: 'ti ti-list-search',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue
index 2d26eb0fb0..4ff5ab09ca 100644
--- a/packages/frontend/src/pages/admin/object-storage.vue
+++ b/packages/frontend/src/pages/admin/object-storage.vue
@@ -149,10 +149,10 @@ function save() {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.objectStorage,
 	icon: 'ti ti-cloud',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue
index b9a34b8bc4..651f0ef936 100644
--- a/packages/frontend/src/pages/admin/other-settings.vue
+++ b/packages/frontend/src/pages/admin/other-settings.vue
@@ -86,8 +86,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.other,
 	icon: 'ti ti-adjustments',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index c580f5781b..1de4dc0dc8 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -184,10 +184,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.dashboard,
 	icon: 'ti ti-dashboard',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/proxy-account.vue b/packages/frontend/src/pages/admin/proxy-account.vue
index 85c9812e85..02b506d13d 100644
--- a/packages/frontend/src/pages/admin/proxy-account.vue
+++ b/packages/frontend/src/pages/admin/proxy-account.vue
@@ -64,8 +64,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.proxyAccount,
 	icon: 'ti ti-ghost',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 0466649e9e..8d77d927d7 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -68,8 +68,8 @@ const headerTabs = computed(() => [{
 	title: 'Inbox',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.jobQueue,
 	icon: 'ti ti-clock-play',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index ea80822e53..04982eea1f 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -84,10 +84,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.relays,
 	icon: 'ti ti-planet',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index 5d0dbe789e..60f06d50ba 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -87,11 +87,8 @@ async function save() {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => role.value ? {
-	title: i18n.ts._role.edit + ': ' + role.value.name,
-	icon: 'ti ti-badge',
-} : {
-	title: i18n.ts._role.new,
+definePageMetadata(() => ({
+	title: role.value ? `${i18n.ts._role.edit}: ${role.value.name}` : i18n.ts._role.new,
 	icon: 'ti ti-badge',
 }));
 </script>
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 87b5dc7f5c..ab8005045b 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -170,10 +170,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
-	title: i18n.ts.role + ': ' + role.name,
+definePageMetadata(() => ({
+	title: `${i18n.ts.role}: ${role.name}`,
 	icon: 'ti ti-badge',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 44b6c0a25c..496cb09664 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -270,10 +270,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.roles,
 	icon: 'ti ti-badges',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 6a0b3fe793..cadcf5a8cc 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -229,8 +229,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.security,
 	icon: 'ti ti-lock',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index daea420988..87318bccce 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -67,10 +67,10 @@ const remove = (index: number): void => {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.serverRules,
 	icon: 'ti ti-checkbox',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index ea57176fff..8af9deae62 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -235,10 +235,10 @@ async function save(): void {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.general,
 	icon: 'ti ti-settings',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 888a27906a..06317760d2 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -137,10 +137,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.users,
 	icon: 'ti ti-users',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/ads.vue b/packages/frontend/src/pages/ads.vue
index e6b1238583..b31807f9f5 100644
--- a/packages/frontend/src/pages/ads.vue
+++ b/packages/frontend/src/pages/ads.vue
@@ -20,9 +20,9 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.ads,
 	icon: 'ti ti-ad',
-});
+}));
 </script>
 
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index 1c4efe91d1..bcd6eb7c0f 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -106,10 +106,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-point',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.announcements,
 	icon: 'ti ti-speakerphone',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 7a48020537..273250d1d0 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -91,10 +91,10 @@ const headerActions = computed(() => antenna.value ? [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => antenna.value ? {
-	title: antenna.value.name,
+definePageMetadata(() => ({
+	title: antenna.value ? antenna.value.name : i18n.ts.antennas,
 	icon: 'ti ti-antenna',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue
index 5e794a975c..30f12a8fb3 100644
--- a/packages/frontend/src/pages/api-console.vue
+++ b/packages/frontend/src/pages/api-console.vue
@@ -87,8 +87,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'API console',
 	icon: 'ti ti-terminal-2',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index b30b2b50e7..d8f8d0b428 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -97,10 +97,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._auth.shareAccessTitle,
 	icon: 'ti ti-apps',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue
index 66f724cd62..ad9ec3c4ee 100644
--- a/packages/frontend/src/pages/avatar-decorations.vue
+++ b/packages/frontend/src/pages/avatar-decorations.vue
@@ -94,8 +94,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.avatarDecorations,
 	icon: 'ti ti-sparkles',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 1c0da4f9bd..d3f4a65b89 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -202,11 +202,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => props.channelId ? {
-	title: i18n.ts._channel.edit,
-	icon: 'ti ti-device-tv',
-} : {
-	title: i18n.ts._channel.create,
+definePageMetadata(() => ({
+	title: props.channelId ? i18n.ts._channel.edit : i18n.ts._channel.create,
 	icon: 'ti ti-device-tv',
 }));
 </script>
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 10cdb70a51..611ae6feca 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -258,10 +258,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-search',
 }]);
 
-definePageMetadata(computed(() => channel.value ? {
-	title: channel.value.name,
+definePageMetadata(() => ({
+	title: channel.value ? channel.value.name : i18n.ts.channel,
 	icon: 'ti ti-device-tv',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 3d3e1c8d78..bde1650754 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -152,8 +152,8 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-edit',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.channel,
 	icon: 'ti ti-device-tv',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue
index fe4080da3d..9e9b5e8688 100644
--- a/packages/frontend/src/pages/clicker.vue
+++ b/packages/frontend/src/pages/clicker.vue
@@ -16,10 +16,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 import MkClickerGame from '@/components/MkClickerGame.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: '🍪👈',
 	icon: 'ti ti-cookie',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index b3f72d889d..c38cc117bc 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -157,10 +157,10 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
 	},
 }] : null);
 
-definePageMetadata(computed(() => clip.value ? {
-	title: clip.value.name,
+definePageMetadata(() => ({
+	title: clip.value ? clip.value.name : i18n.ts.clip,
 	icon: 'ti ti-paperclip',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index ea37b1b398..3e2332e408 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -305,10 +305,10 @@ const headerTabs = computed(() => [{
 	title: i18n.ts.remote,
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.customEmojis,
 	icon: 'ti ti-icons',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue
index 255d07f251..5711ec8b3a 100644
--- a/packages/frontend/src/pages/drive.file.vue
+++ b/packages/frontend/src/pages/drive.file.vue
@@ -48,8 +48,8 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-pencil',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts._fileViewer.title,
 	icon: 'ti ti-file',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/drive.vue b/packages/frontend/src/pages/drive.vue
index fa02319fe0..25e140f67f 100644
--- a/packages/frontend/src/pages/drive.vue
+++ b/packages/frontend/src/pages/drive.vue
@@ -22,9 +22,9 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: folder.value ? folder.value.name : i18n.ts.drive,
 	icon: 'ti ti-cloud',
 	hideHeader: true,
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 0f52835bb7..d9881cebbf 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -1204,10 +1204,10 @@ onDeactivated(() => {
 	bgmNodes?.soundSource.stop();
 });
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.bubbleGame,
 	icon: 'ti ti-apple',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
index d749c14909..1b11457988 100644
--- a/packages/frontend/src/pages/drop-and-fusion.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -121,10 +121,10 @@ function onGameEnd() {
 	gameStarted.value = false;
 }
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.bubbleGame,
 	icon: 'ti ti-device-gamepad',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index 1c1ddb8de0..b1a8183d9b 100644
--- a/packages/frontend/src/pages/explore.vue
+++ b/packages/frontend/src/pages/explore.vue
@@ -60,8 +60,8 @@ const headerTabs = computed(() => [{
 	title: i18n.ts.roles,
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.explore,
 	icon: 'ti ti-hash',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index 1dd6436409..c3d4cae4aa 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -38,10 +38,10 @@ const pagination = {
 	limit: 10,
 };
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.favorites,
 	icon: 'ti ti-star',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index a1981318d1..4418172e62 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -452,9 +452,7 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => flash.value ? {
-	title: i18n.ts._play.edit + ': ' + flash.value.title,
-} : {
-	title: i18n.ts._play.new,
+definePageMetadata(() => ({
+	title: flash.value ? `${i18n.ts._play.edit}: ${flash.value.title}` : i18n.ts._play.new,
 }));
 </script>
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index 7ac06fc427..f63a799365 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -90,8 +90,8 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-heart',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: 'Play',
 	icon: 'ti ti-player-play',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 41c25d6082..4aa3ce1672 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -205,15 +205,17 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => flash.value ? {
-	title: flash.value.title,
-	avatar: flash.value.user,
-	path: `/play/${flash.value.id}`,
-	share: {
-		title: flash.value.title,
-		text: flash.value.summary,
-	},
-} : null));
+definePageMetadata(() => ({
+	title: flash.value ? flash.value.title : 'Play',
+	...flash.value ? {
+		avatar: flash.value.user,
+		path: `/play/${flash.value.id}`,
+		share: {
+			title: flash.value.title,
+			text: flash.value.summary,
+		},
+	} : {},
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index b57a42b92a..8991af8086 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -69,10 +69,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.followRequests,
 	icon: 'ti ti-user-plus',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index 270d125dcd..a68a7e5c41 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -122,11 +122,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => props.postId ? {
-	title: i18n.ts.edit,
-	icon: 'ti ti-pencil',
-} : {
-	title: i18n.ts.postToGallery,
+definePageMetadata(() => ({
+	title: props.postId ? i18n.ts.edit : i18n.ts.postToGallery,
 	icon: 'ti ti-pencil',
 }));
 </script>
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index 70444a5ef4..e0e187f2ce 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -119,10 +119,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-edit',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.gallery,
 	icon: 'ti ti-icons',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index c5c8248f86..615675225d 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -163,10 +163,12 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => post.value ? {
-	title: post.value.title,
-	avatar: post.value.user,
-} : null));
+definePageMetadata(() => ({
+	title: post.value ? post.value.title : i18n.ts.gallery,
+	...post.value ? {
+		avatar: post.value.user,
+	} : {},
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/games.vue b/packages/frontend/src/pages/games.vue
index 606d08e33b..afd6df1ad9 100644
--- a/packages/frontend/src/pages/games.vue
+++ b/packages/frontend/src/pages/games.vue
@@ -27,8 +27,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'Misskey Games',
 	icon: 'ti ti-device-gamepad',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue
index 0b129435c1..4bee437f65 100644
--- a/packages/frontend/src/pages/install-extensions.vue
+++ b/packages/frontend/src/pages/install-extensions.vue
@@ -312,10 +312,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._externalResourceInstaller.title,
 	icon: 'ti ti-download',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 49ea1c64b5..2f1557182a 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -245,10 +245,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-code',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: props.host,
 	icon: 'ti ti-server',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
index cd822ef1db..25e56d2b8d 100644
--- a/packages/frontend/src/pages/invite.vue
+++ b/packages/frontend/src/pages/invite.vue
@@ -93,10 +93,10 @@ async function update() {
 
 update();
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.invite,
 	icon: 'ti ti-user-plus',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue
index b2c178aebe..954246ff93 100644
--- a/packages/frontend/src/pages/list.vue
+++ b/packages/frontend/src/pages/list.vue
@@ -101,10 +101,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list.value ? {
-	title: list.value.name,
+definePageMetadata(() => ({
+	title: list.value ? list.value.name : i18n.ts.lists,
 	icon: 'ti ti-list',
-} : null));
+}));
 </script>
 <style lang="scss" module>
 .main {
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index a545b5267c..ffaf739ed0 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -93,10 +93,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'MiAuth',
 	icon: 'ti ti-apps',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue
index 8b35699e68..8b3b3cfbfd 100644
--- a/packages/frontend/src/pages/my-antennas/create.vue
+++ b/packages/frontend/src/pages/my-antennas/create.vue
@@ -38,8 +38,8 @@ function onAntennaCreated() {
 	router.push('/my/antennas');
 }
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageAntennas,
 	icon: 'ti ti-antenna',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue
index acdeca4ea8..9471be8575 100644
--- a/packages/frontend/src/pages/my-antennas/edit.vue
+++ b/packages/frontend/src/pages/my-antennas/edit.vue
@@ -36,8 +36,8 @@ misskeyApi('antennas/show', { antennaId: props.antennaId }).then((antennaRespons
 	antenna.value = antennaResponse;
 });
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageAntennas,
 	icon: 'ti ti-antenna',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue
index 2e9ea7cc93..21c96348f0 100644
--- a/packages/frontend/src/pages/my-antennas/index.vue
+++ b/packages/frontend/src/pages/my-antennas/index.vue
@@ -55,10 +55,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageAntennas,
 	icon: 'ti ti-antenna',
-});
+}));
 
 onActivated(() => {
 	antennasCache.fetch();
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index 90ef26dbc7..803b28899a 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -104,14 +104,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-heart',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.clip,
 	icon: 'ti ti-paperclip',
-	action: {
-		icon: 'ti ti-plus',
-		handler: create,
-	},
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index bb56415819..82fde284c1 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -71,10 +71,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageLists,
 	icon: 'ti ti-list',
-});
+}));
 
 onActivated(() => {
 	fetch();
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index d7829596db..7492b099ea 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -186,10 +186,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list.value ? {
-	title: list.value.name,
+definePageMetadata(() => ({
+	title: list.value ? list.value.name : i18n.ts.lists,
 	icon: 'ti ti-list',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue
index 3f6d149b04..93a792c42f 100644
--- a/packages/frontend/src/pages/not-found.vue
+++ b/packages/frontend/src/pages/not-found.vue
@@ -31,8 +31,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.notFound,
 	icon: 'ti ti-alert-triangle',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index ae35be969f..4c985b96e6 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -141,16 +141,18 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => note.value ? {
+definePageMetadata(() => ({
 	title: i18n.ts.note,
-	subtitle: dateString(note.value.createdAt),
-	avatar: note.value.user,
-	path: `/notes/${note.value.id}`,
-	share: {
-		title: i18n.tsx.noteOf({ user: note.value.user.name }),
-		text: note.value.text,
-	},
-} : null));
+	...note.value ? {
+		subtitle: dateString(note.value.createdAt),
+		avatar: note.value.user,
+		path: `/notes/${note.value.id}`,
+		share: {
+			title: i18n.tsx.noteOf({ user: note.value.user.name }),
+			text: note.value.text,
+		},
+	} : {},
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index c66af20c4d..7db6fa5395 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -94,10 +94,10 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-mail',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.notifications,
 	icon: 'ti ti-bell',
-})));
+}));
 </script>
 
 <style module lang="scss">
diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue
index f0517b743a..733e34eb2c 100644
--- a/packages/frontend/src/pages/oauth.vue
+++ b/packages/frontend/src/pages/oauth.vue
@@ -51,10 +51,10 @@ function onLogin(res): void {
 	login(res.i);
 }
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'OAuth',
 	icon: 'ti ti-apps',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index a26f2263e2..af32fd2274 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -284,17 +284,11 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-note',
 }]);
 
-definePageMetadata(computed(() => {
-	let title = i18n.ts._pages.newPage;
-	if (props.initPageId) {
-		title = i18n.ts._pages.editPage;
-	} else if (props.initPageName && props.initUser) {
-		title = i18n.ts._pages.readPage;
-	}
-	return {
-		title: title,
-		icon: 'ti ti-pencil',
-	};
+definePageMetadata(() => ({
+	title: props.initPageId ? i18n.ts._pages.editPage
+				: props.initPageName && props.initUser ? i18n.ts._pages.readPage
+				: i18n.ts._pages.newPage,
+	icon: 'ti ti-pencil',
 }));
 </script>
 
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 46d4886412..bece32fc11 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -187,15 +187,17 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => page.value ? {
-	title: page.value.title || page.value.name,
-	avatar: page.value.user,
-	path: `/@${page.value.user.username}/pages/${page.value.name}`,
-	share: {
-		title: page.value.title || page.value.name,
-		text: page.value.summary,
-	},
-} : null));
+definePageMetadata(() => ({
+	title: page.value ? page.value.title || page.value.name : i18n.ts.pages,
+	...page.value ? {
+		avatar: page.value.user,
+		path: `/@${page.value.user.username}/pages/${page.value.name}`,
+		share: {
+			title: page.value.title || page.value.name,
+			text: page.value.summary,
+		},
+	} : {},
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index 06e26ac10d..4ef9d3b091 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -88,8 +88,8 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-heart',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.pages,
 	icon: 'ti ti-note',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue
index fe7ed4f79e..bac1d2bb70 100644
--- a/packages/frontend/src/pages/registry.keys.vue
+++ b/packages/frontend/src/pages/registry.keys.vue
@@ -96,8 +96,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.registry,
 	icon: 'ti ti-adjustments',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue
index 10e0892ed1..c40d13f664 100644
--- a/packages/frontend/src/pages/registry.value.vue
+++ b/packages/frontend/src/pages/registry.value.vue
@@ -123,8 +123,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.registry,
 	icon: 'ti ti-adjustments',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue
index 473687e946..c641874b17 100644
--- a/packages/frontend/src/pages/registry.vue
+++ b/packages/frontend/src/pages/registry.vue
@@ -73,8 +73,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.registry,
 	icon: 'ti ti-adjustments',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue
index 88bf253f56..6b67a9cc87 100644
--- a/packages/frontend/src/pages/reset-password.vue
+++ b/packages/frontend/src/pages/reset-password.vue
@@ -53,8 +53,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.resetPassword,
 	icon: 'ti ti-lock',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue
index 947420477b..eadc51881c 100644
--- a/packages/frontend/src/pages/reversi/game.vue
+++ b/packages/frontend/src/pages/reversi/game.vue
@@ -113,8 +113,8 @@ onUnmounted(() => {
 	}
 });
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: 'Reversi',
 	icon: 'ti ti-device-gamepad',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
index 959ce12583..51a03e4418 100644
--- a/packages/frontend/src/pages/reversi/index.vue
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onDeactivated, onMounted, onUnmounted, ref } from 'vue';
+import { onDeactivated, onMounted, onUnmounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -261,10 +261,10 @@ onUnmounted(() => {
 	cancelMatching();
 });
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: 'Reversi',
 	icon: 'ti ti-device-gamepad',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index 7a4d9b9932..ce80579cf9 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -93,10 +93,10 @@ const headerTabs = computed(() => [{
 	title: i18n.ts.timeline,
 }]);
 
-definePageMetadata(computed(() => ({
-	title: role.value?.name,
+definePageMetadata(() => ({
+	title: role.value ? role.value.name : i18n.ts.role,
 	icon: 'ti ti-badge',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index 3b5990c81e..9aaa8ff9c6 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -152,10 +152,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.scratchpad,
 	icon: 'ti ti-terminal-2',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue
index 911c7d25f3..a3dcda77be 100644
--- a/packages/frontend/src/pages/search.vue
+++ b/packages/frontend/src/pages/search.vue
@@ -52,8 +52,8 @@ const headerTabs = computed(() => [{
 	icon: 'ti ti-users',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.search,
 	icon: 'ti ti-search',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index 8879a4d82a..1182346de9 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -106,10 +106,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.accounts,
 	icon: 'ti ti-users',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/api.vue b/packages/frontend/src/pages/settings/api.vue
index 4c69b88198..d9596b4e45 100644
--- a/packages/frontend/src/pages/settings/api.vue
+++ b/packages/frontend/src/pages/settings/api.vue
@@ -45,8 +45,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'API',
 	icon: 'ti ti-api',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index ad60b4abe1..0e0c1f4c0c 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -75,10 +75,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.installedApps,
 	icon: 'ti ti-plug',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue
index f0cf7cbc8e..3cc911c014 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.vue
@@ -128,10 +128,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.avatarDecorations,
 	icon: 'ti ti-sparkles',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue
index e07e1a4f99..cf05e75acc 100644
--- a/packages/frontend/src/pages/settings/custom-css.vue
+++ b/packages/frontend/src/pages/settings/custom-css.vue
@@ -45,8 +45,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.customCss,
 	icon: 'ti ti-code',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue
index 44578287ee..e574ec7dc0 100644
--- a/packages/frontend/src/pages/settings/deck.vue
+++ b/packages/frontend/src/pages/settings/deck.vue
@@ -36,8 +36,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.deck,
 	icon: 'ti ti-columns',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index 753ade0dd1..b20774c4ec 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -117,10 +117,10 @@ function onContextMenu(ev: MouseEvent, file): void {
 	os.contextMenu(getDriveFileMenu(file), ev);
 }
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.drivecleaner,
 	icon: 'ti ti-trash',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index 6694557c0c..cd38f9850f 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -143,10 +143,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.drive,
 	icon: 'ti ti-cloud',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue
index b70bb8f226..f226647569 100644
--- a/packages/frontend/src/pages/settings/email.vue
+++ b/packages/frontend/src/pages/settings/email.vue
@@ -113,8 +113,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.email,
 	icon: 'ti ti-mail',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index 55fa35ec9f..79969427ec 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -237,10 +237,10 @@ watch(pinnedEmojis, () => {
 	deep: true,
 });
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.emojiPicker,
 	icon: 'ti ti-mood-happy',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index efaffc809c..d13b6884bd 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -465,8 +465,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.general,
 	icon: 'ti ti-adjustments',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index 666d0a5ef1..9bb3957a84 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -225,10 +225,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.importAndExport,
 	icon: 'ti ti-package',
-});
+}));
 </script>
 
 <style module>
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index c4895c8688..5fc1fd1bca 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -28,14 +28,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { ComputedRef, Ref, computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
 import { i18n } from '@/i18n.js';
 import MkInfo from '@/components/MkInfo.vue';
 import MkSuperMenu from '@/components/MkSuperMenu.vue';
 import { signout, $i } from '@/account.js';
 import { clearCache } from '@/scripts/clear-cache.js';
 import { instance } from '@/instance.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import * as os from '@/os.js';
 import { useRouter } from '@/router/supplier.js';
 
@@ -46,7 +46,7 @@ const indexInfo = {
 };
 const INFO = ref(indexInfo);
 const el = shallowRef<HTMLElement | null>(null);
-const childInfo: Ref<ComputedRef<PageMetadata> | null> = ref(null);
+const childInfo = ref<null | PageMetadata>(null);
 
 const router = useRouter();
 
@@ -231,20 +231,22 @@ watch(router.currentRef, (to) => {
 
 const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
 
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	if (info == null) {
 		childInfo.value = null;
 	} else {
 		childInfo.value = info;
-		INFO.value.needWideArea = info.value.needWideArea ?? undefined;
+		INFO.value.needWideArea = info.needWideArea ?? undefined;
 	}
 });
+provideReactiveMetadata(INFO);
 
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(INFO);
+definePageMetadata(() => INFO.value);
 // w 890
 // h 700
 </script>
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index a0619b8ddb..ddc23945dd 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -121,10 +121,10 @@ async function save(): Promise<void> {
 
 init();
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.accountMigration,
 	icon: 'ti ti-plane',
-});
+}));
 </script>
 
 <style lang="scss">
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index 8824e21deb..f4ee7dffbf 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -230,10 +230,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.muteAndBlock,
 	icon: 'ti ti-ban',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index 61347d5b84..7f8460e316 100644
--- a/packages/frontend/src/pages/settings/navbar.vue
+++ b/packages/frontend/src/pages/settings/navbar.vue
@@ -118,10 +118,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.navbar,
 	icon: 'ti ti-list',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index c74ececa12..febcfa32ed 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -117,8 +117,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.notifications,
 	icon: 'ti ti-bell',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index 15db1209fa..a1cb2ea1c4 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -162,8 +162,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.other,
 	icon: 'ti ti-dots',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue
index c076358301..3ab26e80d9 100644
--- a/packages/frontend/src/pages/settings/plugin.install.vue
+++ b/packages/frontend/src/pages/settings/plugin.install.vue
@@ -53,8 +53,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._plugin.install,
 	icon: 'ti ti-download',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue
index 9ee38cd949..0ab75b95a2 100644
--- a/packages/frontend/src/pages/settings/plugin.vue
+++ b/packages/frontend/src/pages/settings/plugin.vue
@@ -125,8 +125,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.plugins,
 	icon: 'ti ti-plug',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 62ca3bfe5b..676159d1b5 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, ref } from 'vue';
+import { onMounted, onUnmounted, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -437,10 +437,10 @@ onUnmounted(() => {
 	connection?.off('registryUpdated');
 });
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: ts.preferencesBackups,
 	icon: 'ti ti-device-floppy',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index 0ef1154c1c..d418be624e 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -118,8 +118,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.privacy,
 	icon: 'ti ti-lock-open',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 5960fbe1dc..60bf9b4d3d 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -260,10 +260,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.profile,
 	icon: 'ti ti-user',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/roles.vue b/packages/frontend/src/pages/settings/roles.vue
index 033e5eb50b..5346a58a79 100644
--- a/packages/frontend/src/pages/settings/roles.vue
+++ b/packages/frontend/src/pages/settings/roles.vue
@@ -37,10 +37,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.roles,
 	icon: 'ti ti-badges',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue
index 95c520db91..de0f63630e 100644
--- a/packages/frontend/src/pages/settings/security.vue
+++ b/packages/frontend/src/pages/settings/security.vue
@@ -103,10 +103,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.security,
 	icon: 'ti ti-lock',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index 962e7c87b4..090f0cf14c 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -94,8 +94,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.sounds,
 	icon: 'ti ti-music',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue
index 7598befd71..1ae3de7994 100644
--- a/packages/frontend/src/pages/settings/statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.vue
@@ -50,8 +50,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.statusbar,
 	icon: 'ti ti-list',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/theme.install.vue b/packages/frontend/src/pages/settings/theme.install.vue
index a3caeb387d..4f05d3784c 100644
--- a/packages/frontend/src/pages/settings/theme.install.vue
+++ b/packages/frontend/src/pages/settings/theme.install.vue
@@ -59,8 +59,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._theme.install,
 	icon: 'ti ti-download',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue
index 0c2e100682..8a94d7388b 100644
--- a/packages/frontend/src/pages/settings/theme.manage.vue
+++ b/packages/frontend/src/pages/settings/theme.manage.vue
@@ -76,8 +76,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._theme.manage,
 	icon: 'ti ti-tool',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index 2e47ef25ef..0a4bd4b826 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -177,10 +177,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.theme,
 	icon: 'ti ti-palette',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index db5d0c6dfd..e9fb1e471e 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -114,8 +114,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'Edit webhook',
 	icon: 'ti ti-webhook',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue
index 7fbabce549..5bf85e48f4 100644
--- a/packages/frontend/src/pages/settings/webhook.new.vue
+++ b/packages/frontend/src/pages/settings/webhook.new.vue
@@ -82,8 +82,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'Create new webhook',
 	icon: 'ti ti-webhook',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue
index 282492c62a..0d11b00c97 100644
--- a/packages/frontend/src/pages/settings/webhook.vue
+++ b/packages/frontend/src/pages/settings/webhook.vue
@@ -50,8 +50,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'Webhook',
 	icon: 'ti ti-webhook',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue
index 816a1d3b58..680934e7ce 100644
--- a/packages/frontend/src/pages/share.vue
+++ b/packages/frontend/src/pages/share.vue
@@ -172,8 +172,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.share,
 	icon: 'ti ti-share',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index 15814bfdd4..9b77392872 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -55,10 +55,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: props.tag,
 	icon: 'ti ti-hash',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 250fe0d16b..50c3beeabc 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -228,10 +228,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.themeEditor,
 	icon: 'ti ti-palette',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index efe86be2f3..48dfc1fd44 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -337,10 +337,10 @@ const headerTabsWhenNotLogin = computed(() => [
 	}] : []),
 ] as Tab[]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.timeline,
 	icon: src.value === 'local' ? 'ti ti-planet' : src.value === 'social' ? 'ti ti-universe' : src.value === 'global' ? 'ti ti-whirl' : 'ti ti-home',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index 0eeed29363..de6737f37d 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -70,10 +70,10 @@ const headerActions = computed(() => list.value ? [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list.value ? {
-	title: list.value.name,
+definePageMetadata(() => ({
+	title: list.value ? list.value.name : i18n.ts.lists,
 	icon: 'ti ti-list',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/user-tag.vue b/packages/frontend/src/pages/user-tag.vue
index 3b31750bdd..a77493fe47 100644
--- a/packages/frontend/src/pages/user-tag.vue
+++ b/packages/frontend/src/pages/user-tag.vue
@@ -34,9 +34,9 @@ const tagUsers = computed(() => ({
 	},
 }));
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: props.tag,
 	icon: 'ti ti-user-search',
-})));
+}));
 </script>
 
diff --git a/packages/frontend/src/pages/user/followers.vue b/packages/frontend/src/pages/user/followers.vue
index 94d426aaeb..70883242e5 100644
--- a/packages/frontend/src/pages/user/followers.vue
+++ b/packages/frontend/src/pages/user/followers.vue
@@ -52,11 +52,14 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => user.value ? {
+definePageMetadata(() => ({
+	title: i18n.ts.user,
 	icon: 'ti ti-user',
-	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
-	subtitle: i18n.ts.followers,
-	userName: user.value,
-	avatar: user.value,
-} : null));
+	...user.value ? {
+		title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
+		subtitle: i18n.ts.followers,
+		userName: user.value,
+		avatar: user.value,
+	} : {},
+}));
 </script>
diff --git a/packages/frontend/src/pages/user/following.vue b/packages/frontend/src/pages/user/following.vue
index b874cf16e0..37b25f694f 100644
--- a/packages/frontend/src/pages/user/following.vue
+++ b/packages/frontend/src/pages/user/following.vue
@@ -52,11 +52,14 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => user.value ? {
+definePageMetadata(() => ({
+	title: i18n.ts.user,
 	icon: 'ti ti-user',
-	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
-	subtitle: i18n.ts.following,
-	userName: user.value,
-	avatar: user.value,
-} : null));
+	...user.value ? {
+		title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
+		subtitle: i18n.ts.following,
+		userName: user.value,
+		avatar: user.value,
+	} : {},
+}));
 </script>
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index a277dc424f..a6244e2a93 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -126,15 +126,18 @@ const headerTabs = computed(() => user.value ? [{
 	icon: 'ti ti-code',
 }] : []);
 
-definePageMetadata(computed(() => user.value ? {
+definePageMetadata(() => ({
+	title: i18n.ts.user,
 	icon: 'ti ti-user',
-	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
-	subtitle: `@${getAcct(user.value)}`,
-	userName: user.value,
-	avatar: user.value,
-	path: `/@${user.value.username}`,
-	share: {
-		title: user.value.name,
-	},
-} : null));
+	...user.value ? {
+		title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
+		subtitle: `@${getAcct(user.value)}`,
+		userName: user.value,
+		avatar: user.value,
+		path: `/@${user.value.username}`,
+		share: {
+			title: user.value.name,
+		},
+	} : {},
+}));
 </script>
diff --git a/packages/frontend/src/pages/welcome.vue b/packages/frontend/src/pages/welcome.vue
index 22a991784e..9ba6a5885e 100644
--- a/packages/frontend/src/pages/welcome.vue
+++ b/packages/frontend/src/pages/welcome.vue
@@ -29,8 +29,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: instanceName,
 	icon: null,
-})));
+}));
 </script>
diff --git a/packages/frontend/src/scripts/page-metadata.ts b/packages/frontend/src/scripts/page-metadata.ts
index 974634c928..0e3b093ecf 100644
--- a/packages/frontend/src/scripts/page-metadata.ts
+++ b/packages/frontend/src/scripts/page-metadata.ts
@@ -4,10 +4,7 @@
  */
 
 import * as Misskey from 'misskey-js';
-import { ComputedRef, inject, isRef, onActivated, onMounted, provide, ref, Ref } from 'vue';
-
-export const setPageMetadata = Symbol('setPageMetadata');
-export const pageMetadataProvider = Symbol('pageMetadataProvider');
+import { MaybeRefOrGetter, Ref, inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
 
 export type PageMetadata = {
 	title: string;
@@ -18,29 +15,56 @@ export type PageMetadata = {
 	needWideArea?: boolean;
 };
 
-export function definePageMetadata(metadata: PageMetadata | null | Ref<PageMetadata | null> | ComputedRef<PageMetadata | null>): void {
-	const _metadata = isRef(metadata) ? metadata : ref(metadata);
+type PageMetadataGetter = () => PageMetadata;
+type PageMetadataReceiver = (getter: PageMetadataGetter) => void;
 
-	provide(pageMetadataProvider, _metadata);
+const RECEIVER_KEY = Symbol('ReceiverKey');
+const setReceiver = (v: PageMetadataReceiver): void => {
+	provide<PageMetadataReceiver>(RECEIVER_KEY, v);
+};
+const getReceiver = (): PageMetadataReceiver | undefined => {
+	return inject<PageMetadataReceiver>(RECEIVER_KEY);
+};
 
-	const set = inject(setPageMetadata) as any;
-	if (set) {
-		set(_metadata);
+const METADATA_KEY = Symbol('MetadataKey');
+const setMetadata = (v: Ref<PageMetadata | null>): void => {
+	provide<Ref<PageMetadata | null>>(METADATA_KEY, v);
+};
+const getMetadata = (): Ref<PageMetadata | null> | undefined => {
+	return inject<Ref<PageMetadata | null>>(METADATA_KEY);
+};
 
-		onMounted(() => {
-			set(_metadata);
-		});
+export const definePageMetadata = (maybeRefOrGetterMetadata: MaybeRefOrGetter<PageMetadata>): void => {
+	const metadataRef = ref(toValue(maybeRefOrGetterMetadata));
+	const metadataGetter = () => metadataRef.value;
+	const receiver = getReceiver();
 
-		onActivated(() => {
-			set(_metadata);
-		});
-	}
-}
+	// setup handler
+	receiver?.(metadataGetter);
 
-export function provideMetadataReceiver(callback: (info: ComputedRef<PageMetadata>) => void): void {
-	provide(setPageMetadata, callback);
-}
+	// update handler
+	onBeforeUnmount(watch(
+		() => toValue(maybeRefOrGetterMetadata),
+		(metadata) => {
+			metadataRef.value = metadata;
+			receiver?.(metadataGetter);
+		},
+		{ deep: true },
+	));
+	onActivated(() => {
+		receiver?.(metadataGetter);
+	});
+};
 
-export function injectPageMetadata(): PageMetadata | undefined {
-	return inject(pageMetadataProvider);
-}
+export const provideMetadataReceiver = (receiver: PageMetadataReceiver): void => {
+	setReceiver(receiver);
+};
+
+export const provideReactiveMetadata = (metadataRef: Ref<PageMetadata | null>): void => {
+	setMetadata(metadataRef);
+};
+
+export const injectReactiveMetadata = (): Ref<PageMetadata | null> => {
+	const metadataRef = getMetadata();
+	return isRef(metadataRef) ? metadataRef : ref(null);
+};
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 6e28a86c29..b833e9f6be 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -52,7 +52,7 @@ import XCommon from './_common_/common.vue';
 import { instanceName } from '@/config.js';
 import { StickySidebar } from '@/scripts/sticky-sidebar.js';
 import * as os from '@/os.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
@@ -60,11 +60,13 @@ import { mainRouter } from '@/router/main.js';
 const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue'));
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
 const DESKTOP_THRESHOLD = 1100;
 
 const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
 
-const pageMetadata = ref<null | PageMetadata>();
+const pageMetadata = ref<null | PageMetadata>(null);
 const widgetsShowing = ref(false);
 const fullView = ref(false);
 const globalHeaderHeight = ref(0);
@@ -75,12 +77,18 @@ const widgetsLeft = ref<HTMLElement>();
 const widgetsRight = ref<HTMLElement>();
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
-	pageMetadata.value = info.value;
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
+	pageMetadata.value = info;
 	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 provide('shouldHeaderThin', showMenuOnTop.value);
 provide('forceSpacerMin', true);
 
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index b7c48e6a3a..79c9671917 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :isStacked="isStacked">
 	<template #header>
-		<template v-if="pageMetadata?.value">
-			<i :class="pageMetadata?.value.icon"></i>
-			{{ pageMetadata?.value.title }}
+		<template v-if="pageMetadata">
+			<i :class="pageMetadata.icon"></i>
+			{{ pageMetadata.title }}
 		</template>
 	</template>
 
@@ -19,12 +19,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, provide, shallowRef, ref } from 'vue';
+import { provide, shallowRef, ref } from 'vue';
 import XColumn from './column.vue';
 import { deckStore, Column } from '@/ui/deck/deck-store.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
 import { mainRouter } from '@/router/main.js';
@@ -35,12 +35,14 @@ defineProps<{
 }>();
 
 const contents = shallowRef<HTMLElement>();
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | PageMetadata>(null);
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
 });
+provideReactiveMetadata(pageMetadata);
 
 /*
 function back() {
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index 67bbeb3074..db5eb19c20 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -14,21 +14,29 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { provide, ComputedRef, ref } from 'vue';
+import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { instanceName } from '@/config.js';
 import { mainRouter } from '@/router/main.js';
 
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
+const pageMetadata = ref<null | PageMetadata>(null);
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
-	if (pageMetadata.value.value) {
-		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
+	if (pageMetadata.value) {
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 document.documentElement.style.overflowY = 'scroll';
 </script>
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 2129441d28..3cb6f598a6 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 	<div v-if="isMobile" ref="navFooter" :class="$style.nav">
 		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
-		<button :class="$style.navButton" class="_button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
+		<button :class="$style.navButton" class="_button" @click="isRoot ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
 		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')">
 			<i :class="$style.navButtonIcon" class="ti ti-bell"></i>
 			<span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator">
@@ -105,7 +105,7 @@ import { defaultStore } from '@/store.js';
 import { navbarItemDef } from '@/navbar.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { CURRENT_STICKY_BOTTOM } from '@/const.js';
@@ -117,6 +117,8 @@ const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
 const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
 
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
 const DESKTOP_THRESHOLD = 1100;
 const MOBILE_THRESHOLD = 500;
 
@@ -127,18 +129,24 @@ window.addEventListener('resize', () => {
 	isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
 });
 
-const pageMetadata = ref<null | PageMetadata>();
+const pageMetadata = ref<null | PageMetadata>(null);
 const widgetsShowing = ref(false);
 const navFooter = shallowRef<HTMLElement>();
 const contents = shallowRef<InstanceType<typeof MkStickyContainer>>();
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
-	pageMetadata.value = info.value;
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
+	pageMetadata.value = info;
 	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 const menuIndicated = computed(() => {
 	for (const def in navbarItemDef) {
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 05c73efe28..29b305d9bc 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div class="mk-app">
-	<a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
+	<a v-if="isRoot" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
 
-	<div v-if="!narrow && !root" class="side">
+	<div v-if="!narrow && !isRoot" class="side">
 		<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
 		<div class="dashboard">
 			<MkVisitorDashboard/>
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 
 	<div class="main">
-		<div v-if="!root" class="header">
+		<div v-if="!isRoot" class="header">
 			<div v-if="narrow === false" class="wide">
 				<MkA to="/" class="link" activeClass="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA>
 				<MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA>
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 		</div>
 		<div class="contents">
-			<main v-if="!root" style="container-type: inline-size;">
+			<main v-if="!isRoot" style="container-type: inline-size;">
 				<RouterView/>
 			</main>
 			<main v-else>
@@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, onMounted, provide, ref, computed } from 'vue';
+import { onMounted, provide, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import XCommon from './_common_/common.vue';
 import { instanceName } from '@/config.js';
@@ -79,22 +79,30 @@ import { instance } from '@/instance.js';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
 import { ColdDeviceStorage, defaultStore } from '@/store.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
 import { mainRouter } from '@/router/main.js';
 
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
 const DESKTOP_THRESHOLD = 1100;
 
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | PageMetadata>(null);
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
-	if (pageMetadata.value.value) {
-		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
+	if (pageMetadata.value) {
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 const announcements = {
 	endpoint: 'announcements',
@@ -120,8 +128,6 @@ const keymap = computed(() => {
 	};
 });
 
-const root = computed(() => mainRouter.currentRoute.value.name === 'index');
-
 misskeyApi('meta', { detail: true }).then(res => {
 	meta.value = res;
 });
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index 6e68abf771..bb8cffaf52 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -22,24 +22,32 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { provide, ComputedRef, ref } from 'vue';
+import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { instanceName, ui } from '@/config.js';
 import { i18n } from '@/i18n.js';
 import { mainRouter } from '@/router/main.js';
 
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
+const pageMetadata = ref<null | PageMetadata>(null);
 
 const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck';
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
-	if (pageMetadata.value.value) {
-		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
+	if (pageMetadata.value) {
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 function goToMisskey() {
 	window.location.href = '/';
-- 
GitLab