diff --git a/src/web/app/app.vue b/src/web/app/app.vue
index 497d47003f..321e003930 100644
--- a/src/web/app/app.vue
+++ b/src/web/app/app.vue
@@ -1,3 +1,3 @@
 <template>
-	<router-view></router-view>
+	<router-view id="app"></router-view>
 </template>
diff --git a/src/web/app/common/mios.ts b/src/web/app/common/mios.ts
index a37c5d6f78..e3a66f5b11 100644
--- a/src/web/app/common/mios.ts
+++ b/src/web/app/common/mios.ts
@@ -1,3 +1,4 @@
+import Vue from 'vue';
 import { EventEmitter } from 'eventemitter3';
 import api from './scripts/api';
 import signout from './scripts/signout';
@@ -8,6 +9,8 @@ import ServerStreamManager from './scripts/streaming/server-stream-manager';
 import RequestsStreamManager from './scripts/streaming/requests-stream-manager';
 import MessagingIndexStreamManager from './scripts/streaming/messaging-index-stream-manager';
 
+import Err from '../common/views/components/connect-failed.vue';
+
 //#region environment variables
 declare const _VERSION_: string;
 declare const _LANG_: string;
@@ -214,8 +217,10 @@ export default class MiOS extends EventEmitter {
 			// When failure
 			.catch(() => {
 				// Render the error screen
-				//document.body.innerHTML = '<mk-error />';
-				//riot.mount('*');
+				document.body.innerHTML = '<div id="err"></div>';
+				new Vue({
+					render: createEl => createEl(Err)
+				}).$mount('#err');
 
 				Progress.done();
 			});
diff --git a/src/web/app/common/views/components/connect-failed.troubleshooter.vue b/src/web/app/common/views/components/connect-failed.troubleshooter.vue
index 49396d1584..bede504b58 100644
--- a/src/web/app/common/views/components/connect-failed.troubleshooter.vue
+++ b/src/web/app/common/views/components/connect-failed.troubleshooter.vue
@@ -41,8 +41,8 @@ export default Vue.extend({
 		return {
 			network: navigator.onLine,
 			end: false,
-			internet: false,
-			server: false
+			internet: null,
+			server: null
 		};
 	},
 	mounted() {
diff --git a/src/web/app/mobile/views/pages/welcome.vue b/src/web/app/mobile/views/pages/welcome.vue
index 959d8cfcad..84e5ae5507 100644
--- a/src/web/app/mobile/views/pages/welcome.vue
+++ b/src/web/app/mobile/views/pages/welcome.vue
@@ -1,5 +1,146 @@
 <template>
-<div>
-	<mk-signin/>
+<div class="welcome">
+	<h1><b>Misskey</b>へようこそ</h1>
+	<p>Twitter風ミニブログSNS、Misskeyへようこそ。思ったことを投稿したり、タイムラインでみんなの投稿を読むこともできます。</p>
+	<div class="form">
+		<p>ログイン</p>
+		<div>
+			<form @submit.prevent="onSubmit">
+				<input v-model="username" type="text" pattern="^[a-zA-Z0-9-]+$" placeholder="ユーザー名" autofocus required @change="onUsernameChange"/>
+				<input v-model="password" type="password" placeholder="パスワード" required/>
+				<input v-if="user && user.two_factor_enabled" v-model="token" type="number" placeholder="トークン" required/>
+				<button type="submit" :disabled="signing">{{ signing ? 'ログインしています' : 'ログイン' }}</button>
+			</form>
+			<div>
+				<a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a>
+			</div>
+		</div>
+	</div>
+	<a href="/signup">アカウントを作成する</a>
 </div>
 </template>
+
+<script lang="ts">
+import Vue from 'vue';
+import { apiUrl } from '../../../config';
+
+export default Vue.extend({
+	data() {
+		return {
+			signing: false,
+			user: null,
+			username: '',
+			password: '',
+			token: '',
+			apiUrl
+		};
+	},
+	mounted() {
+		document.documentElement.style.background = '#293946';
+	},
+	methods: {
+		onUsernameChange() {
+			(this as any).api('users/show', {
+				username: this.username
+			}).then(user => {
+				this.user = user;
+			});
+		},
+		onSubmit() {
+			this.signing = true;
+
+			(this as any).api('signin', {
+				username: this.username,
+				password: this.password,
+				token: this.user && this.user.two_factor_enabled ? this.token : undefined
+			}).then(() => {
+				location.reload();
+			}).catch(() => {
+				alert('something happened');
+				this.signing = false;
+			});
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.welcome
+	padding 16px
+	margin 0 auto
+	max-width 500px
+
+	h1
+		margin 0
+		padding 8px
+		font-size 1.5em
+		font-weight normal
+		color #c3c6ca
+
+		& + p
+			margin 0 0 16px 0
+			padding 0 8px 0 8px
+			color #949fa9
+
+	.form
+		background #fff
+		border solid 1px rgba(0, 0, 0, 0.2)
+		border-radius 8px
+		overflow hidden
+
+		& + a
+			display block
+			margin-top 16px
+			text-align center
+
+		> p
+			margin 0
+			padding 12px 20px
+			color #555
+			background #f5f5f5
+			border-bottom solid 1px #ddd
+
+		> div
+
+			> form
+				padding 16px
+				border-bottom solid 1px #ddd
+
+				input
+					display block
+					padding 12px
+					margin 0 0 16px 0
+					width 100%
+					font-size 1em
+					color rgba(0, 0, 0, 0.7)
+					background #fff
+					outline none
+					border solid 1px #ddd
+					border-radius 4px
+
+				button
+					display block
+					width 100%
+					padding 10px
+					margin 0
+					color #333
+					font-size 1em
+					text-align center
+					text-decoration none
+					text-shadow 0 1px 0 rgba(255, 255, 255, 0.9)
+					background-image linear-gradient(#fafafa, #eaeaea)
+					border 1px solid #ddd
+					border-bottom-color #cecece
+					border-radius 4px
+
+					&:active
+						background-color #767676
+						background-image none
+						border-color #444
+						box-shadow 0 1px 3px rgba(0, 0, 0, 0.075), inset 0 0 5px rgba(0, 0, 0, 0.2)
+
+			> div
+				padding 16px
+				text-align center
+
+</style>
diff --git a/webpack/module/rules/base64.ts b/webpack/module/rules/base64.ts
deleted file mode 100644
index c2f6b9339e..0000000000
--- a/webpack/module/rules/base64.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Replace base64 symbols
- */
-
-import * as fs from 'fs';
-
-export default () => ({
-	enforce: 'pre',
-	test: /\.(vue|js)$/,
-	exclude: /node_modules/,
-	loader: 'string-replace-loader',
-	query: {
-		search: /%base64:(.+?)%/g,
-		replace: (_, key) => {
-			return fs.readFileSync(__dirname + '/../../../src/web/' + key, 'base64');
-		}
-	}
-});
diff --git a/webpack/webpack.config.ts b/webpack/webpack.config.ts
index bd8c6d1205..76d2980788 100644
--- a/webpack/webpack.config.ts
+++ b/webpack/webpack.config.ts
@@ -2,6 +2,7 @@
  * webpack configuration
  */
 
+import * as fs from 'fs';
 const minify = require('html-minifier').minify;
 import I18nReplacer from '../src/common/build/i18n';
 import { pattern as faPattern, replacement as faReplacement } from '../src/common/build/fa';
@@ -19,7 +20,11 @@ global['collapseSpacesReplacement'] = html => {
 		collapseWhitespace: true,
 		collapseInlineTagWhitespace: true,
 		keepClosingSlash: true
-	});
+	}).replace(/\t/g, '');
+};
+
+global['base64replacement'] = (_, key) => {
+	return fs.readFileSync(__dirname + '/../src/web/' + key, 'base64');
 };
 
 module.exports = Object.keys(langs).map(lang => {
@@ -59,6 +64,12 @@ module.exports = Object.keys(langs).map(lang => {
 						cssSourceMap: false,
 						preserveWhitespace: false
 					}
+				}, {
+					loader: 'replace',
+					query: {
+						search: /%base64:(.+?)%/g.toString(),
+						replace: 'base64replacement'
+					}
 				}, {
 					loader: 'webpack-replace-loader',
 					options: {