diff --git a/CHANGELOG.md b/CHANGELOG.md
index 873716a3d5..1139707b5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,12 +2,19 @@
 ## 12.x.x (unreleased)
 
 ### Improvements
-- ページロードエラーページにリロードボタンを追加
 
 ### Bugfixes
 
 -->
 
+## 12.x.x (unreleased)
+
+### Improvements
+- クライアント: ユーザーのリアクション一覧を見れるように
+- API: ユーザーのリアクション一覧を取得する users/reactions を追加
+
+### Bugfixes
+
 ## 12.92.0 (2021/10/16)
 
 ### Improvements
diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue
index 0ddf73d572..6811dff2db 100644
--- a/src/client/pages/user/index.vue
+++ b/src/client/pages/user/index.vue
@@ -181,6 +181,7 @@
 				</template>
 				<XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/>
 				<XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/>
+				<XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/>
 				<XClips v-else-if="page === 'clips'" :user="user" class="_gap"/>
 				<XPages v-else-if="page === 'pages'" :user="user" class="_gap"/>
 				<XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/>
@@ -223,6 +224,7 @@ export default defineComponent({
 		MkTab,
 		MkInfo,
 		XFollowList: defineAsyncComponent(() => import('./follow-list.vue')),
+		XReactions: defineAsyncComponent(() => import('./reactions.vue')),
 		XClips: defineAsyncComponent(() => import('./clips.vue')),
 		XPages: defineAsyncComponent(() => import('./pages.vue')),
 		XGallery: defineAsyncComponent(() => import('./gallery.vue')),
@@ -268,6 +270,11 @@ export default defineComponent({
 					title: this.$ts.overview,
 					icon: 'fas fa-home',
 					onClick: () => { this.$router.push('/@' + getAcct(this.user)); },
+				}, {
+					active: this.page === 'reactions',
+					title: this.$ts.reaction,
+					icon: 'fas fa-laugh',
+					onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); },
 				}, {
 					active: this.page === 'clips',
 					title: this.$ts.clips,
diff --git a/src/client/pages/user/reactions.vue b/src/client/pages/user/reactions.vue
new file mode 100644
index 0000000000..5ac7e01027
--- /dev/null
+++ b/src/client/pages/user/reactions.vue
@@ -0,0 +1,81 @@
+<template>
+<div>
+	<MkPagination :pagination="pagination" #default="{items}" ref="list">
+		<div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap afdcfbfb">
+			<div class="header">
+				<MkAvatar class="avatar" :user="user"/>
+				<MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/>
+				<MkTime :time="item.createdAt" class="createdAt"/>
+			</div>
+			<MkNote :note="item.note" @update:note="updated(note, $event)" :key="item.id"/>
+		</div>
+	</MkPagination>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import MkPagination from '@client/components/ui/pagination.vue';
+import MkNote from '@client/components/note.vue';
+import MkReactionIcon from '@client/components/reaction-icon.vue';
+
+export default defineComponent({
+	components: {
+		MkPagination,
+		MkNote,
+		MkReactionIcon,
+	},
+
+	props: {
+		user: {
+			type: Object,
+			required: true
+		},
+	},
+
+	data() {
+		return {
+			pagination: {
+				endpoint: 'users/reactions',
+				limit: 20,
+				params: {
+					userId: this.user.id,
+				}
+			},
+		};
+	},
+
+	watch: {
+		user() {
+			this.$refs.list.reload();
+		}
+	},
+});
+</script>
+
+<style lang="scss" scoped>
+.afdcfbfb {
+	> .header {
+		display: flex;
+		align-items: center;
+		padding: 8px 16px;
+		margin-bottom: 8px;
+		border-bottom: solid 2px var(--divider);
+
+		> .avatar {
+			width: 24px;
+			height: 24px;
+			margin-right: 8px;
+		}
+
+		> .reaction {
+			width: 32px;
+			height: 32px;
+		}
+
+		> .createdAt {
+			margin-left: auto;
+		}
+	}
+}
+</style>
diff --git a/src/models/repositories/note-reaction.ts b/src/models/repositories/note-reaction.ts
index ba74076f6c..5d86065526 100644
--- a/src/models/repositories/note-reaction.ts
+++ b/src/models/repositories/note-reaction.ts
@@ -1,6 +1,6 @@
 import { EntityRepository, Repository } from 'typeorm';
 import { NoteReaction } from '@/models/entities/note-reaction';
-import { Users } from '../index';
+import { Notes, Users } from '../index';
 import { Packed } from '@/misc/schema';
 import { convertLegacyReaction } from '@/misc/reaction-lib';
 import { User } from '@/models/entities/user';
@@ -9,8 +9,15 @@ import { User } from '@/models/entities/user';
 export class NoteReactionRepository extends Repository<NoteReaction> {
 	public async pack(
 		src: NoteReaction['id'] | NoteReaction,
-		me?: { id: User['id'] } | null | undefined
+		me?: { id: User['id'] } | null | undefined,
+		options?: {
+			withNote: boolean;
+		},
 	): Promise<Packed<'NoteReaction'>> {
+		const opts = Object.assign({
+			withNote: false,
+		}, options);
+
 		const reaction = typeof src === 'object' ? src : await this.findOneOrFail(src);
 
 		return {
@@ -18,6 +25,9 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
 			createdAt: reaction.createdAt.toISOString(),
 			user: await Users.pack(reaction.userId, me),
 			type: convertLegacyReaction(reaction.reaction),
+			...(opts.withNote ? {
+				note: await Notes.pack(reaction.noteId, me),
+			} : {})
 		};
 	}
 }
diff --git a/src/server/api/endpoints/users/reactions.ts b/src/server/api/endpoints/users/reactions.ts
new file mode 100644
index 0000000000..44d7887482
--- /dev/null
+++ b/src/server/api/endpoints/users/reactions.ts
@@ -0,0 +1,67 @@
+import $ from 'cafy';
+import { ID } from '@/misc/cafy-id';
+import define from '../../define';
+import { NoteReactions } from '@/models/index';
+import { makePaginationQuery } from '../../common/make-pagination-query';
+import { generateVisibilityQuery } from '../../common/generate-visibility-query';
+
+export const meta = {
+	tags: ['users', 'reactions'],
+
+	requireCredential: false as const,
+
+	params: {
+		userId: {
+			validator: $.type(ID),
+		},
+
+		limit: {
+			validator: $.optional.num.range(1, 100),
+			default: 10,
+		},
+
+		sinceId: {
+			validator: $.optional.type(ID),
+		},
+
+		untilId: {
+			validator: $.optional.type(ID),
+		},
+
+		sinceDate: {
+			validator: $.optional.num,
+		},
+
+		untilDate: {
+			validator: $.optional.num,
+		},
+	},
+
+	res: {
+		type: 'array' as const,
+		optional: false as const, nullable: false as const,
+		items: {
+			type: 'object' as const,
+			optional: false as const, nullable: false as const,
+			ref: 'NoteReaction',
+		}
+	},
+
+	errors: {
+	}
+};
+
+export default define(meta, async (ps, me) => {
+	const query = makePaginationQuery(NoteReactions.createQueryBuilder('reaction'),
+			ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+		.andWhere(`reaction.userId = :userId`, { userId: ps.userId })
+		.leftJoinAndSelect('reaction.note', 'note');
+
+	generateVisibilityQuery(query, me);
+
+	const reactions = await query
+		.take(ps.limit!)
+		.getMany();
+
+	return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true })));
+});