diff --git a/package.json b/package.json
index 727c4af716..033f76c30a 100644
--- a/package.json
+++ b/package.json
@@ -182,6 +182,7 @@
 		"uuid": "3.2.1",
 		"vhost": "3.0.2",
 		"vue": "^2.5.13",
+		"vue-cropperjs": "^2.2.0",
 		"vue-js-modal": "^1.3.9",
 		"vue-loader": "^14.1.1",
 		"vue-router": "^3.0.1",
diff --git a/src/web/app/desktop/-tags/crop-window.tag b/src/web/app/desktop/-tags/crop-window.tag
deleted file mode 100644
index c26f74b121..0000000000
--- a/src/web/app/desktop/-tags/crop-window.tag
+++ /dev/null
@@ -1,196 +0,0 @@
-<mk-crop-window>
-	<mk-window ref="window" is-modal={ true } width={ '800px' }>
-		<yield to="header">%fa:crop%{ parent.title }</yield>
-		<yield to="content">
-			<div class="body"><img ref="img" src={ parent.image.url + '?thumbnail&quality=80' } alt=""/></div>
-			<div class="action">
-				<button class="skip" @click="parent.skip">クロップをスキップ</button>
-				<button class="cancel" @click="parent.cancel">キャンセル</button>
-				<button class="ok" @click="parent.ok">決定</button>
-			</div>
-		</yield>
-	</mk-window>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-
-			> mk-window
-				[data-yield='header']
-					> [data-fa]
-						margin-right 4px
-
-				[data-yield='content']
-
-					> .body
-						> img
-							width 100%
-							max-height 400px
-
-					.cropper-modal {
-						opacity: 0.8;
-					}
-
-					.cropper-view-box {
-						outline-color: $theme-color;
-					}
-
-					.cropper-line, .cropper-point {
-						background-color: $theme-color;
-					}
-
-					.cropper-bg {
-						animation: cropper-bg 0.5s linear infinite;
-					}
-
-					@-webkit-keyframes cropper-bg {
-						0% {
-							background-position: 0 0;
-						}
-
-						100% {
-							background-position: -8px -8px;
-						}
-					}
-
-					@-moz-keyframes cropper-bg {
-						0% {
-							background-position: 0 0;
-						}
-
-						100% {
-							background-position: -8px -8px;
-						}
-					}
-
-					@-ms-keyframes cropper-bg {
-						0% {
-							background-position: 0 0;
-						}
-
-						100% {
-							background-position: -8px -8px;
-						}
-					}
-
-					@keyframes cropper-bg {
-						0% {
-							background-position: 0 0;
-						}
-
-						100% {
-							background-position: -8px -8px;
-						}
-					}
-
-					> .action
-						height 72px
-						background lighten($theme-color, 95%)
-
-						.ok
-						.cancel
-						.skip
-							display block
-							position absolute
-							bottom 16px
-							cursor pointer
-							padding 0
-							margin 0
-							height 40px
-							font-size 1em
-							outline none
-							border-radius 4px
-
-							&:focus
-								&:after
-									content ""
-									pointer-events none
-									position absolute
-									top -5px
-									right -5px
-									bottom -5px
-									left -5px
-									border 2px solid rgba($theme-color, 0.3)
-									border-radius 8px
-
-							&:disabled
-								opacity 0.7
-								cursor default
-
-						.ok
-						.cancel
-							width 120px
-
-						.ok
-							right 16px
-							color $theme-color-foreground
-							background linear-gradient(to bottom, lighten($theme-color, 25%) 0%, lighten($theme-color, 10%) 100%)
-							border solid 1px lighten($theme-color, 15%)
-
-							&:not(:disabled)
-								font-weight bold
-
-							&:hover:not(:disabled)
-								background linear-gradient(to bottom, lighten($theme-color, 8%) 0%, darken($theme-color, 8%) 100%)
-								border-color $theme-color
-
-							&:active:not(:disabled)
-								background $theme-color
-								border-color $theme-color
-
-						.cancel
-						.skip
-							color #888
-							background linear-gradient(to bottom, #ffffff 0%, #f5f5f5 100%)
-							border solid 1px #e2e2e2
-
-							&:hover
-								background linear-gradient(to bottom, #f9f9f9 0%, #ececec 100%)
-								border-color #dcdcdc
-
-							&:active
-								background #ececec
-								border-color #dcdcdc
-
-						.cancel
-							right 148px
-
-						.skip
-							left 16px
-							width 150px
-
-	</style>
-	<script lang="typescript">
-		const Cropper = require('cropperjs');
-
-		this.image = this.opts.file;
-		this.title = this.opts.title;
-		this.aspectRatio = this.opts.aspectRatio;
-		this.cropper = null;
-
-		this.on('mount', () => {
-			this.img = this.$refs.window.refs.img;
-			this.cropper = new Cropper(this.img, {
-				aspectRatio: this.aspectRatio,
-				highlight: false,
-				viewMode: 1
-			});
-		});
-
-		this.ok = () => {
-			this.cropper.getCroppedCanvas().toBlob(blob => {
-				this.$emit('cropped', blob);
-				this.$refs.window.close();
-			});
-		};
-
-		this.skip = () => {
-			this.$emit('skipped');
-			this.$refs.window.close();
-		};
-
-		this.cancel = () => {
-			this.$emit('canceled');
-			this.$refs.window.close();
-		};
-	</script>
-</mk-crop-window>
diff --git a/src/web/app/desktop/views/components/crop-window.vue b/src/web/app/desktop/views/components/crop-window.vue
new file mode 100644
index 0000000000..2ba62a3a68
--- /dev/null
+++ b/src/web/app/desktop/views/components/crop-window.vue
@@ -0,0 +1,169 @@
+<template>
+	<mk-window ref="window" is-modal width="800px">
+		<span slot="header">%fa:crop%{{ title }}</span>
+		<div class="body">
+			<vue-cropper
+				:src="image.url"
+				:view-mode="1"
+			/>
+		</div>
+		<div :class="$style.actions">
+			<button :class="$style.skip" @click="skip">クロップをスキップ</button>
+			<button :class="$style.cancel" @click="cancel">キャンセル</button>
+			<button :class="$style.ok" @click="ok">決定</button>
+		</div>
+	</mk-window>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+	props: {
+		image: {
+			type: Object,
+			required: true
+		},
+		title: {
+			type: String,
+			required: true
+		},
+		aspectRatio: {
+			type: Number,
+			required: true
+		}
+	},
+	methods: {
+		ok() {
+			(this.$refs.cropper as any).getCroppedCanvas().toBlob(blob => {
+				this.$emit('cropped', blob);
+				(this.$refs.window as any).close();
+			});
+		},
+
+		skip() {
+			this.$emit('skipped');
+			(this.$refs.window as any).close();
+		},
+
+		cancel() {
+			this.$emit('canceled');
+			(this.$refs.window as any).close();
+		}
+	}
+});
+</script>
+
+<style lang="stylus" module>
+.header
+	> [data-fa]
+		margin-right 4px
+
+.img
+	width 100%
+	max-height 400px
+
+.actions
+	height 72px
+	background lighten($theme-color, 95%)
+
+.ok
+.cancel
+.skip
+	display block
+	position absolute
+	bottom 16px
+	cursor pointer
+	padding 0
+	margin 0
+	height 40px
+	font-size 1em
+	outline none
+	border-radius 4px
+
+	&:focus
+		&:after
+			content ""
+			pointer-events none
+			position absolute
+			top -5px
+			right -5px
+			bottom -5px
+			left -5px
+			border 2px solid rgba($theme-color, 0.3)
+			border-radius 8px
+
+	&:disabled
+		opacity 0.7
+		cursor default
+
+.ok
+.cancel
+	width 120px
+
+.ok
+	right 16px
+	color $theme-color-foreground
+	background linear-gradient(to bottom, lighten($theme-color, 25%) 0%, lighten($theme-color, 10%) 100%)
+	border solid 1px lighten($theme-color, 15%)
+
+	&:not(:disabled)
+		font-weight bold
+
+	&:hover:not(:disabled)
+		background linear-gradient(to bottom, lighten($theme-color, 8%) 0%, darken($theme-color, 8%) 100%)
+		border-color $theme-color
+
+	&:active:not(:disabled)
+		background $theme-color
+		border-color $theme-color
+
+.cancel
+.skip
+	color #888
+	background linear-gradient(to bottom, #ffffff 0%, #f5f5f5 100%)
+	border solid 1px #e2e2e2
+
+	&:hover
+		background linear-gradient(to bottom, #f9f9f9 0%, #ececec 100%)
+		border-color #dcdcdc
+
+	&:active
+		background #ececec
+		border-color #dcdcdc
+
+.cancel
+	right 148px
+
+.skip
+	left 16px
+	width 150px
+
+</style>
+
+<style lang="stylus">
+.cropper-modal {
+	opacity: 0.8;
+}
+
+.cropper-view-box {
+	outline-color: $theme-color;
+}
+
+.cropper-line, .cropper-point {
+	background-color: $theme-color;
+}
+
+.cropper-bg {
+	animation: cropper-bg 0.5s linear infinite;
+}
+
+@keyframes cropper-bg {
+	0% {
+		background-position: 0 0;
+	}
+
+	100% {
+		background-position: -8px -8px;
+	}
+}
+</style>
diff --git a/src/web/app/desktop/views/pages/user/user.header.vue b/src/web/app/desktop/views/pages/user/user.header.vue
index 67d110f2f3..6c8375f163 100644
--- a/src/web/app/desktop/views/pages/user/user.header.vue
+++ b/src/web/app/desktop/views/pages/user/user.header.vue
@@ -12,9 +12,9 @@
 			<p class="location" v-if="user.profile.location">%fa:map-marker%{{ user.profile.location }}</p>
 		</div>
 		<footer>
-			<a :href="`/${user.username}`" :data-active="$parent.page == 'home'">%fa:home%概要</a>
-			<a :href="`/${user.username}/media`" :data-active="$parent.page == 'media'">%fa:image%メディア</a>
-			<a :href="`/${user.username}/graphs`" :data-active="$parent.page == 'graphs'">%fa:chart-bar%グラフ</a>
+			<router-link :to="`/${user.username}`" :data-active="$parent.page == 'home'">%fa:home%概要</router-link>
+			<router-link :to="`/${user.username}/media`" :data-active="$parent.page == 'media'">%fa:image%メディア</router-link>
+			<router-link :to="`/${user.username}/graphs`" :data-active="$parent.page == 'graphs'">%fa:chart-bar%グラフ</router-link>
 		</footer>
 	</div>
 </div>