diff --git a/CHANGELOG.md b/CHANGELOG.md
index a61051e32b03ec5f625a653c233e6ea094da16aa..0e61e7f35c538f17366ebadc56c4c321386f5342 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,7 @@
 - ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加
 - アカウント初期設定ウィザードに戻るボタンを追加
 - アカウントの初期設定ウィザードにあとでボタンを追加
+- サーバーにカスタム絵文字の種類が多い場合のパフォーマンスの改善
 - Fix: URLプレビューで情報が取得できなかった際の挙動を修正
 - Fix: Safari、Firefoxでの新規登録時、パスワードマネージャーにメールアドレスが登録されていた挙動を修正
 - Fix: ロールタイムラインが無効でも投稿が流れてしまう問題の修正
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index d489b3e9b92fde459883ee1d2883a502df7d38b5..b632baaa2bbea2b85cec62b046443070d4af99b7 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -101,7 +101,7 @@ import { isTouchUsing } from '@/scripts/touch';
 import { deviceKind } from '@/scripts/device-kind';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
-import { customEmojiCategories, customEmojis } from '@/custom-emojis';
+import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis';
 import { $i } from '@/account';
 
 const props = withDefaults(defineProps<{
@@ -337,7 +337,7 @@ function done(query?: string): boolean | void {
 	if (query == null || typeof query !== 'string') return;
 
 	const q2 = query.replace(/:/g, '');
-	const exactMatchCustom = customEmojis.value.find(emoji => emoji.name === q2);
+	const exactMatchCustom = customEmojisMap.get(q2);
 	if (exactMatchCustom) {
 		chosen(exactMatchCustom);
 		return true;
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index 0cb31ffcba5ce5d47e7a1290963c3da166d00a30..e8a7f17cc6af0ed79a93d2fa688750ff97fae8e9 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -7,7 +7,7 @@
 import { computed } from 'vue';
 import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy';
 import { defaultStore } from '@/store';
-import { customEmojis } from '@/custom-emojis';
+import { customEmojisMap } from '@/custom-emojis';
 
 const props = defineProps<{
 	name: string;
@@ -26,7 +26,7 @@ const rawUrl = computed(() => {
 		return props.url;
 	}
 	if (isLocal.value) {
-		return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null;
+		return customEmojisMap.get(customEmojiName.value)?.url ?? null;
 	}
 	return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`;
 });
diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts
index de1b5b8a6314c3cec3de0524fe446e1160fdbeb8..9b738b2fd46357e2d6f294f91934562c563d33f9 100644
--- a/packages/frontend/src/custom-emojis.ts
+++ b/packages/frontend/src/custom-emojis.ts
@@ -1,4 +1,4 @@
-import { shallowRef, computed, markRaw } from 'vue';
+import { shallowRef, computed, markRaw, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { api, apiGet } from './os';
 import { useStream } from '@/stream';
@@ -16,6 +16,14 @@ export const customEmojiCategories = computed<[ ...string[], null ]>(() => {
 	return markRaw([...Array.from(categories), null]);
 });
 
+export const customEmojisMap = new Map<string, Misskey.entities.CustomEmoji>();
+watch(customEmojis, emojis => {
+	customEmojisMap.clear();
+	for (const emoji of emojis) {
+		customEmojisMap.set(emoji.name, emoji);
+	}
+}, { immediate: true });
+
 // TODO: ここら辺副作用なのでいい感じにする
 const stream = useStream();