diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
index 300615ec58..d505b38dcc 100644
--- a/src/client/app/mobile/script.ts
+++ b/src/client/app/mobile/script.ts
@@ -42,6 +42,7 @@ import MkUserLists from './views/pages/user-lists.vue';
 import MkUserList from './views/pages/user-list.vue';
 import MkSettings from './views/pages/settings.vue';
 import MkOthello from './views/pages/othello.vue';
+import MkTag from './views/pages/tag.vue';
 
 Vue.use(MdCard);
 Vue.use(MdButton);
@@ -88,6 +89,7 @@ init((launch) => {
 			{ path: '/i/drive/file/:file', component: MkDrive },
 			{ path: '/selectdrive', component: MkSelectDrive },
 			{ path: '/search', component: MkSearch },
+			{ path: '/tags/:tag', component: MkTag },
 			{ path: '/othello', name: 'othello', component: MkOthello },
 			{ path: '/othello/:game', component: MkOthello },
 			{ path: '/@:user', component: MkUser },
diff --git a/src/client/app/mobile/views/pages/tag.vue b/src/client/app/mobile/views/pages/tag.vue
new file mode 100644
index 0000000000..b4c993e667
--- /dev/null
+++ b/src/client/app/mobile/views/pages/tag.vue
@@ -0,0 +1,81 @@
+<template>
+<mk-ui>
+	<span slot="header">%fa:hashtag%{{ $route.params.tag }}</span>
+
+	<main>
+		<p v-if="!fetching && empty">%fa:search%「{{ q }}」に関する投稿は見つかりませんでした。</p>
+		<mk-notes ref="timeline" :more="existMore ? more : null"/>
+	</main>
+</mk-ui>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import Progress from '../../../common/scripts/loading';
+
+const limit = 20;
+
+export default Vue.extend({
+	data() {
+		return {
+			fetching: true,
+			moreFetching: false,
+			existMore: false,
+			offset: 0,
+			empty: false
+		};
+	},
+	watch: {
+		$route: 'fetch'
+	},
+	mounted() {
+		this.$nextTick(() => {
+			this.fetch();
+		});
+	},
+	methods: {
+		fetch() {
+			this.fetching = true;
+			Progress.start();
+
+			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
+				(this as any).api('notes/search_by_tag', {
+					limit: limit + 1,
+					offset: this.offset,
+					tag: this.$route.params.tag
+				}).then(notes => {
+					if (notes.length == 0) this.empty = true;
+					if (notes.length == limit + 1) {
+						notes.pop();
+						this.existMore = true;
+					}
+					res(notes);
+					this.fetching = false;
+					Progress.done();
+				}, rej);
+			}));
+		},
+		more() {
+			this.offset += limit;
+
+			const promise = (this as any).api('notes/search_by_tag', {
+				limit: limit + 1,
+				offset: this.offset,
+				tag: this.$route.params.tag
+			});
+
+			promise.then(notes => {
+				if (notes.length == limit + 1) {
+					notes.pop();
+				} else {
+					this.existMore = false;
+				}
+				notes.forEach(n => (this.$refs.timeline as any).append(n));
+				this.moreFetching = false;
+			});
+
+			return promise;
+		}
+	}
+});
+</script>