diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index c72cc2ab1b85b38b8adfe9898ad15dfd6e4bd36b..1875b507ca877cc0b67f81fd3d1cce9b6cf7056a 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -10,7 +10,8 @@ import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from 'vu
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
-type Captcha = {
+// APIs provided by Captcha services
+export type Captcha = {
 	render(container: string | Node, options: {
 		readonly [_ in 'sitekey' | 'theme' | 'type' | 'size' | 'tabindex' | 'callback' | 'expired' | 'expired-callback' | 'error-callback' | 'endpoint']?: unknown;
 	}): string;
@@ -32,7 +33,7 @@ declare global {
 
 const props = defineProps<{
 	provider: CaptchaProvider;
-	sitekey: string;
+	sitekey: string | null; // null will show error on request
 	modelValue?: string | null;
 }>();
 
diff --git a/packages/frontend/src/components/MkSignup.vue b/packages/frontend/src/components/MkSignup.vue
index 22a8063809b7a8410f44b08c3c7592aeae6e0c43..30279148f80063e2f034fabd5a76100ae2bd9251 100644
--- a/packages/frontend/src/components/MkSignup.vue
+++ b/packages/frontend/src/components/MkSignup.vue
@@ -72,7 +72,7 @@ import { toUnicode } from 'punycode/';
 import MkButton from './MkButton.vue';
 import MkInput from './MkInput.vue';
 import MkSwitch from './MkSwitch.vue';
-import MkCaptcha from '@/components/MkCaptcha.vue';
+import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
 import * as config from '@/config';
 import * as os from '@/os';
 import { login } from '@/account';
@@ -92,9 +92,9 @@ const emit = defineEmits<{
 
 const host = toUnicode(config.host);
 
-let hcaptcha = $ref();
-let recaptcha = $ref();
-let turnstile = $ref();
+let hcaptcha = $ref<Captcha | undefined>();
+let recaptcha = $ref<Captcha | undefined>();
+let turnstile = $ref<Captcha | undefined>();
 
 let username: string = $ref('');
 let password: string = $ref('');
@@ -208,19 +208,20 @@ function onChangePasswordRetype(): void {
 	passwordRetypeState = password === retypedPassword ? 'match' : 'not-match';
 }
 
-function onSubmit(): void {
+async function onSubmit(): Promise<void> {
 	if (submitting) return;
 	submitting = true;
 
-	os.api('signup', {
-		username,
-		password,
-		emailAddress: email,
-		invitationCode,
-		'hcaptcha-response': hCaptchaResponse,
-		'g-recaptcha-response': reCaptchaResponse,
-		'turnstile-response': turnstileResponse,
-	}).then(() => {
+	try {
+		await os.api('signup', {
+			username,
+			password,
+			emailAddress: email,
+			invitationCode,
+			'hcaptcha-response': hCaptchaResponse,
+			'g-recaptcha-response': reCaptchaResponse,
+			'turnstile-response': turnstileResponse,
+		});
 		if (instance.emailRequiredForSignup) {
 			os.alert({
 				type: 'success',
@@ -229,28 +230,27 @@ function onSubmit(): void {
 			});
 			emit('signupEmailPending');
 		} else {
-			os.api('signin', {
+			const res = await os.api('signin', {
 				username,
 				password,
-			}).then(res => {
-				emit('signup', res);
-
-				if (props.autoSet) {
-					login(res.i);
-				}
 			});
+			emit('signup', res);
+
+			if (props.autoSet) {
+				return login(res.i);
+			}
 		}
-	}).catch(() => {
+	} catch {
 		submitting = false;
-		hcaptcha.reset?.();
-		recaptcha.reset?.();
-		turnstile.reset?.();
+		hcaptcha?.reset?.();
+		recaptcha?.reset?.();
+		turnstile?.reset?.();
 
 		os.alert({
 			type: 'error',
 			text: i18n.ts.somethingHappened,
 		});
-	});
+	}
 }
 </script>