diff --git a/src/client/components/form/suspense.vue b/src/client/components/form/suspense.vue
index 2a48faccb3..d04dc07624 100644
--- a/src/client/components/form/suspense.vue
+++ b/src/client/components/form/suspense.vue
@@ -9,9 +9,9 @@
 		<slot :result="result"></slot>
 	</div>
 	<div class="_formItem" v-else>
-		<div class="_formPanel">
-			error!
-			<button @click="retry">retry</button>
+		<div class="_formPanel eiurkvay">
+			<div><i class="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</div>
+			<MkButton inline @click="retry" class="retry"><i class="fas fa-redo-alt"></i> {{ $ts.retry }}</MkButton>
 		</div>
 	</div>
 </transition>
@@ -20,8 +20,13 @@
 <script lang="ts">
 import { defineComponent, PropType, ref, watch } from 'vue';
 import './form.scss';
+import MkButton from '@client/components/ui/button.vue';
 
 export default defineComponent({
+	components: {
+		MkButton
+	},
+
 	props: {
 		p: {
 			type: Function as PropType<() => Promise<any>>,
@@ -84,4 +89,13 @@ export default defineComponent({
 .fade-leave-to {
 	opacity: 0;
 }
+
+.eiurkvay {
+	padding: 16px;
+	text-align: center;
+
+	> .retry {
+		margin-top: 16px;
+	}
+}
 </style>
diff --git a/src/client/pages/instance/file-dialog.vue b/src/client/pages/instance/file-dialog.vue
index ae6755465c..74a755fa15 100644
--- a/src/client/pages/instance/file-dialog.vue
+++ b/src/client/pages/instance/file-dialog.vue
@@ -82,7 +82,7 @@ export default defineComponent({
 		},
 
 		showUser() {
-			os.pageWindow(`/instance/user/${this.file.userId}`);
+			os.pageWindow(`/user-info/${this.file.userId}`);
 		},
 
 		async del() {
diff --git a/src/client/pages/instance/user.vue b/src/client/pages/instance/user.vue
deleted file mode 100644
index fbc10a3672..0000000000
--- a/src/client/pages/instance/user.vue
+++ /dev/null
@@ -1,229 +0,0 @@
-<template>
-<FormBase>
-	<FormSuspense :p="init">
-		<div class="_formItem aeakzknw">
-			<MkAvatar class="avatar" :user="user" :show-indicator="true"/>
-		</div>
-
-		<FormLink :to="userPage(user)">Profile</FormLink>
-
-		<FormGroup>
-			<FormKeyValueView>
-				<template #key>Acct</template>
-				<template #value><span class="_monospace">{{ acct(user) }}</span></template>
-			</FormKeyValueView>
-
-			<FormKeyValueView>
-				<template #key>ID</template>
-				<template #value><span class="_monospace">{{ user.id }}</span></template>
-			</FormKeyValueView>
-		</FormGroup>
-
-		<FormGroup>
-			<FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" @update:value="toggleModerator" v-model:value="moderator">{{ $ts.moderator }}</FormSwitch>
-			<FormSwitch @update:value="toggleSilence" v-model:value="silenced">{{ $ts.silence }}</FormSwitch>
-			<FormSwitch @update:value="toggleSuspend" v-model:value="suspended">{{ $ts.suspend }}</FormSwitch>
-		</FormGroup>
-
-		<FormGroup>
-			<FormButton v-if="user.host != null" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
-			<FormButton v-if="user.host == null" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
-		</FormGroup>
-
-		<FormGroup>
-			<FormLink :to="`/user-ap-info/${user.id}`">ActivityPub</FormLink>
-
-			<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink>
-			<FormKeyValueView v-else>
-				<template #key>{{ $ts.instanceInfo }}</template>
-				<template #value>(Local user)</template>
-			</FormKeyValueView>
-		</FormGroup>
-
-		<FormGroup>
-			<FormKeyValueView>
-				<template #key>{{ $ts.updatedAt }}</template>
-				<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
-			</FormKeyValueView>
-		</FormGroup>
-
-		<FormObjectView tall :value="user">
-			<span>Raw</span>
-		</FormObjectView>
-	</FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { computed, defineAsyncComponent, defineComponent } from 'vue';
-import FormObjectView from '@client/components/form/object-view.vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormLink from '@client/components/form/link.vue';
-import FormBase from '@client/components/form/base.vue';
-import FormGroup from '@client/components/form/group.vue';
-import FormButton from '@client/components/form/button.vue';
-import FormKeyValueView from '@client/components/form/key-value-view.vue';
-import FormSuspense from '@client/components/form/suspense.vue';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import bytes from '@client/filters/bytes';
-import * as symbols from '@client/symbols';
-import { url } from '@client/config';
-import { userPage, acct } from '@client/filters/user';
-
-export default defineComponent({
-	components: {
-		FormBase,
-		FormSwitch,
-		FormObjectView,
-		FormButton,
-		FormLink,
-		FormGroup,
-		FormKeyValueView,
-		FormSuspense,
-	},
-
-	props: {
-		userId: {
-			type: String,
-			required: true
-		}
-	},
-
-	data() {
-		return {
-			[symbols.PAGE_INFO]: computed(() => ({
-				title: this.$ts.userInfo,
-				icon: 'fas fa-info-circle',
-				actions: this.user ? [this.user.url ? {
-					text: this.user.url,
-					icon: 'fas fa-external-link-alt',
-					handler: () => {
-						window.open(this.user.url, '_blank');
-					}
-				} : undefined].filter(x => x !== undefined) : [],
-			})),
-			init: null,
-			user: null,
-			info: null,
-			moderator: false,
-			silenced: false,
-			suspended: false,
-		}
-	},
-
-	watch: {
-		userId: {
-			handler() {
-				this.init = this.createFetcher();
-			},
-			immediate: true
-		}
-	},
-
-	methods: {
-		number,
-		bytes,
-		userPage,
-		acct,
-
-		createFetcher() {
-			return () => Promise.all([os.api('users/show', {
-				userId: this.userId
-			}), os.api('admin/show-user', {
-				userId: this.userId
-			})]).then(([user, info]) => {
-				this.user = user;
-				this.info = info;
-				this.moderator = this.info.isModerator;
-				this.silenced = this.info.isSilenced;
-				this.suspended = this.info.isSuspended;
-			});
-		},
-
-		refreshUser() {
-			this.init = this.createFetcher();
-		},
-
-		async updateRemoteUser() {
-			await os.apiWithDialog('admin/update-remote-user', { userId: this.user.id });
-			this.refreshUser();
-		},
-
-		async resetPassword() {
-			os.apiWithDialog('admin/reset-password', {
-				userId: this.user.id,
-			}, undefined, ({ password }) => {
-				os.dialog({
-					type: 'success',
-					text: this.$t('newPasswordIs', { password })
-				});
-			});
-		},
-
-		async toggleSilence(v) {
-			const confirm = await os.dialog({
-				type: 'warning',
-				showCancelButton: true,
-				text: v ? this.$ts.silenceConfirm : this.$ts.unsilenceConfirm,
-			});
-			if (confirm.canceled) {
-				this.silenced = !v;
-			} else {
-				await os.api(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: this.user.id });
-				await this.refreshUser();
-			}
-		},
-
-		async toggleSuspend(v) {
-			const confirm = await os.dialog({
-				type: 'warning',
-				showCancelButton: true,
-				text: v ? this.$ts.suspendConfirm : this.$ts.unsuspendConfirm,
-			});
-			if (confirm.canceled) {
-				this.suspended = !v;
-			} else {
-				await os.api(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: this.user.id });
-				await this.refreshUser();
-			}
-		},
-
-		async toggleModerator(v) {
-			await os.api(v ? 'admin/moderators/add' : 'admin/moderators/remove', { userId: this.user.id });
-			await this.refreshUser();
-		},
-
-		async deleteAllFiles() {
-			const confirm = await os.dialog({
-				type: 'warning',
-				showCancelButton: true,
-				text: this.$ts.deleteAllFilesConfirm,
-			});
-			if (confirm.canceled) return;
-			const process = async () => {
-				await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id });
-				os.success();
-			};
-			await process().catch(e => {
-				os.dialog({
-					type: 'error',
-					text: e.toString()
-				});
-			});
-			await this.refreshUser();
-		},
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.aeakzknw {
-	> .avatar {
-		display: block;
-		margin: 0 auto;
-		width: 64px;
-		height: 64px;
-	}
-}
-</style>
diff --git a/src/client/pages/instance/users.vue b/src/client/pages/instance/users.vue
index 452886abde..2808b70fba 100644
--- a/src/client/pages/instance/users.vue
+++ b/src/client/pages/instance/users.vue
@@ -162,7 +162,7 @@ export default defineComponent({
 		},
 
 		show(user) {
-			os.pageWindow(`/instance/user/${user.id}`);
+			os.pageWindow(`/user-info/${user.id}`);
 		},
 
 		acct
diff --git a/src/client/pages/user-info.vue b/src/client/pages/user-info.vue
index 378fbb7b50..090e8cdda8 100644
--- a/src/client/pages/user-info.vue
+++ b/src/client/pages/user-info.vue
@@ -1,35 +1,55 @@
 <template>
 <FormBase>
 	<FormSuspense :p="init">
+		<div class="_formItem aeakzknw">
+			<MkAvatar class="avatar" :user="user" :show-indicator="true"/>
+		</div>
+
+		<FormLink :to="userPage(user)">Profile</FormLink>
+
 		<FormGroup>
-			<template #label><MkAcct :user="user"/></template>
+			<FormKeyValueView>
+				<template #key>Acct</template>
+				<template #value><span class="_monospace">{{ acct(user) }}</span></template>
+			</FormKeyValueView>
 
 			<FormKeyValueView>
 				<template #key>ID</template>
 				<template #value><span class="_monospace">{{ user.id }}</span></template>
 			</FormKeyValueView>
-
-			<FormGroup>
-				<FormLink :to="`/user-ap-info/${user.id}`">ActivityPub</FormLink>
-
-				<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink>
-				<FormKeyValueView v-else>
-					<template #key>{{ $ts.instanceInfo }}</template>
-					<template #value>(Local user)</template>
-				</FormKeyValueView>
-			</FormGroup>
-
-			<FormGroup>
-				<FormKeyValueView>
-					<template #key>{{ $ts.updatedAt }}</template>
-					<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
-				</FormKeyValueView>
-			</FormGroup>
-
-			<FormObjectView tall :value="user">
-				<span>Raw</span>
-			</FormObjectView>
 		</FormGroup>
+
+		<FormGroup v-if="iAmModerator">
+			<FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" @update:value="toggleModerator" v-model:value="moderator">{{ $ts.moderator }}</FormSwitch>
+			<FormSwitch @update:value="toggleSilence" v-model:value="silenced">{{ $ts.silence }}</FormSwitch>
+			<FormSwitch @update:value="toggleSuspend" v-model:value="suspended">{{ $ts.suspend }}</FormSwitch>
+		</FormGroup>
+
+		<FormGroup>
+			<FormButton v-if="user.host != null" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
+			<FormButton v-if="user.host == null && iAmModerator" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
+		</FormGroup>
+
+		<FormGroup>
+			<FormLink :to="`/user-ap-info/${user.id}`">ActivityPub</FormLink>
+
+			<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink>
+			<FormKeyValueView v-else>
+				<template #key>{{ $ts.instanceInfo }}</template>
+				<template #value>(Local user)</template>
+			</FormKeyValueView>
+		</FormGroup>
+
+		<FormGroup>
+			<FormKeyValueView>
+				<template #key>{{ $ts.updatedAt }}</template>
+				<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
+			</FormKeyValueView>
+		</FormGroup>
+
+		<FormObjectView tall :value="user">
+			<span>Raw</span>
+		</FormObjectView>
 	</FormSuspense>
 </FormBase>
 </template>
@@ -38,6 +58,7 @@
 import { computed, defineAsyncComponent, defineComponent } from 'vue';
 import FormObjectView from '@client/components/form/object-view.vue';
 import FormTextarea from '@client/components/form/textarea.vue';
+import FormSwitch from '@client/components/form/switch.vue';
 import FormLink from '@client/components/form/link.vue';
 import FormBase from '@client/components/form/base.vue';
 import FormGroup from '@client/components/form/group.vue';
@@ -49,11 +70,13 @@ import number from '@client/filters/number';
 import bytes from '@client/filters/bytes';
 import * as symbols from '@client/symbols';
 import { url } from '@client/config';
+import { userPage, acct } from '@client/filters/user';
 
 export default defineComponent({
 	components: {
 		FormBase,
 		FormTextarea,
+		FormSwitch,
 		FormObjectView,
 		FormButton,
 		FormLink,
@@ -72,7 +95,7 @@ export default defineComponent({
 	data() {
 		return {
 			[symbols.PAGE_INFO]: computed(() => ({
-				title: this.$ts.userInfo,
+				title: this.user ? acct(this.user) : this.$ts.userInfo,
 				icon: 'fas fa-info-circle',
 				actions: this.user ? [this.user.url ? {
 					text: this.user.url,
@@ -84,17 +107,23 @@ export default defineComponent({
 			})),
 			init: null,
 			user: null,
+			info: null,
+			moderator: false,
+			silenced: false,
+			suspended: false,
+		}
+	},
+
+	computed: {
+		iAmModerator(): boolean {
+			return this.$i && (this.$i.isAdmin || this.$i.isModerator);
 		}
 	},
 
 	watch: {
 		userId: {
 			handler() {
-				this.init = () => os.api('users/show', {
-					userId: this.userId
-				}).then(user => {
-					this.user = user;
-				});
+				this.init = this.createFetcher();
 			},
 			immediate: true
 		}
@@ -103,6 +132,114 @@ export default defineComponent({
 	methods: {
 		number,
 		bytes,
+		userPage,
+		acct,
+
+		createFetcher() {
+			if (this.iAmModerator) {
+				return () => Promise.all([os.api('users/show', {
+					userId: this.userId
+				}), os.api('admin/show-user', {
+					userId: this.userId
+				})]).then(([user, info]) => {
+					this.user = user;
+					this.info = info;
+					this.moderator = this.info.isModerator;
+					this.silenced = this.info.isSilenced;
+					this.suspended = this.info.isSuspended;
+				});
+			} else {
+				return () => os.api('users/show', {
+					userId: this.userId
+				}).then((user) => {
+					this.user = user;
+				});
+			}
+		},
+
+		refreshUser() {
+			this.init = this.createFetcher();
+		},
+
+		async updateRemoteUser() {
+			await os.apiWithDialog('admin/update-remote-user', { userId: this.user.id });
+			this.refreshUser();
+		},
+
+		async resetPassword() {
+			os.apiWithDialog('admin/reset-password', {
+				userId: this.user.id,
+			}, undefined, ({ password }) => {
+				os.dialog({
+					type: 'success',
+					text: this.$t('newPasswordIs', { password })
+				});
+			});
+		},
+
+		async toggleSilence(v) {
+			const confirm = await os.dialog({
+				type: 'warning',
+				showCancelButton: true,
+				text: v ? this.$ts.silenceConfirm : this.$ts.unsilenceConfirm,
+			});
+			if (confirm.canceled) {
+				this.silenced = !v;
+			} else {
+				await os.api(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: this.user.id });
+				await this.refreshUser();
+			}
+		},
+
+		async toggleSuspend(v) {
+			const confirm = await os.dialog({
+				type: 'warning',
+				showCancelButton: true,
+				text: v ? this.$ts.suspendConfirm : this.$ts.unsuspendConfirm,
+			});
+			if (confirm.canceled) {
+				this.suspended = !v;
+			} else {
+				await os.api(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: this.user.id });
+				await this.refreshUser();
+			}
+		},
+
+		async toggleModerator(v) {
+			await os.api(v ? 'admin/moderators/add' : 'admin/moderators/remove', { userId: this.user.id });
+			await this.refreshUser();
+		},
+
+		async deleteAllFiles() {
+			const confirm = await os.dialog({
+				type: 'warning',
+				showCancelButton: true,
+				text: this.$ts.deleteAllFilesConfirm,
+			});
+			if (confirm.canceled) return;
+			const process = async () => {
+				await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id });
+				os.success();
+			};
+			await process().catch(e => {
+				os.dialog({
+					type: 'error',
+					text: e.toString()
+				});
+			});
+			await this.refreshUser();
+		},
 	}
 });
 </script>
+
+<style lang="scss" scoped>
+.aeakzknw {
+	> .avatar {
+		display: block;
+		margin: 0 auto;
+		width: 64px;
+		height: 64px;
+	}
+}
+</style>
diff --git a/src/client/router.ts b/src/client/router.ts
index 93de287ea5..26a4dac499 100644
--- a/src/client/router.ts
+++ b/src/client/router.ts
@@ -59,7 +59,6 @@ export const router = createRouter({
 		{ path: '/my/antennas', component: page('my-antennas/index') },
 		{ path: '/my/clips', component: page('my-clips/index') },
 		{ path: '/scratchpad', component: page('scratchpad') },
-		{ path: '/instance/user/:user', component: page('instance/user'), props: route => ({ userId: route.params.user }) },
 		{ path: '/instance/:page(.*)?', component: page('instance/index'), props: route => ({ initialPage: route.params.page || null }) },
 		{ path: '/instance', component: page('instance/index') },
 		{ path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) },
diff --git a/src/client/scripts/get-user-menu.ts b/src/client/scripts/get-user-menu.ts
index 9a003b5c38..ceb2bfe173 100644
--- a/src/client/scripts/get-user-menu.ts
+++ b/src/client/scripts/get-user-menu.ts
@@ -124,13 +124,7 @@ export function getUserMenu(user) {
 		action: () => {
 			copyToClipboard(`@${user.username}@${user.host || host}`);
 		}
-	}, ($i && ($i.isAdmin || $i.isModerator)) ? {
-		icon: 'fas fa-info-circle',
-		text: i18n.locale.info,
-		action: () => {
-			os.pageWindow(`/instance/user/${user.id}`);
-		}
-	} : {
+	}, {
 		icon: 'fas fa-info-circle',
 		text: i18n.locale.info,
 		action: () => {
diff --git a/src/client/scripts/lookup-user.ts b/src/client/scripts/lookup-user.ts
index 1bcfd8e9db..269777d874 100644
--- a/src/client/scripts/lookup-user.ts
+++ b/src/client/scripts/lookup-user.ts
@@ -10,7 +10,7 @@ export async function lookupUser() {
 	if (canceled) return;
 
 	const show = (user) => {
-		os.pageWindow(`/instance/user/${user.id}`);
+		os.pageWindow(`/user-info/${user.id}`);
 	};
 
 	const usernamePromise = os.api('users/show', parseAcct(result));