This example demonstrates how to integrate the SpeedOf.Me speed test API with Vue.js 3.
A working example built on the Vue 3 CDN build is linked from this folder's page. Opening the source file directly will not run a test: the API validates the page URL against your registered domain, so serve your copy over http or https from that domain.
The index.html file shows how to use Vue 3 with the CDN build. Great for demos.
npm create vite@latest my-speedtest -- --template vue
cd my-speedtest
npm install
index.html:
<script src="https://speedof.me/api/api.js"></script>
src/composables/useSpeedTest.js):
import { ref, onMounted } from 'vue';
export function useSpeedTest({ apiKey, domain, sustainTime = 6 }) {
const status = ref('idle');
const result = ref(null);
const progress = ref(null);
const error = ref(null);
onMounted(() => {
window.SomApi.account = apiKey;
window.SomApi.domainName = domain;
window.SomApi.config.sustainTime = sustainTime;
window.SomApi.onTestCompleted = (testResult) => {
result.value = testResult;
progress.value = null;
status.value = 'completed';
};
window.SomApi.onProgress = (data) => {
progress.value = data;
};
window.SomApi.onError = (err) => {
error.value = err;
status.value = 'error';
};
});
function startTest() {
status.value = 'running';
result.value = null;
error.value = null;
window.SomApi.startTest();
}
return { status, result, progress, error, startTest };
}
src/components/SpeedTest.vue):
<script setup>
import { useSpeedTest } from '../composables/useSpeedTest';
const { status, result, progress, error, startTest } = useSpeedTest({
apiKey: 'YOUR_API_KEY',
domain: 'your-domain.com'
});
</script>
<template>
<div>
<button @click="startTest" :disabled="status === 'running'">
{{ status === 'running' ? 'Testing...' : 'Start Test' }}
</button>
<div v-if="result">
<p>Download: {{ result.download }} Mbps</p>
<p>Upload: {{ result.upload }} Mbps</p>
</div>
</div>
</template>
// Required
window.SomApi.account = 'YOUR_API_KEY';
window.SomApi.domainName = 'your-domain.com';
// Optional
window.SomApi.config.sustainTime = 6; // 1-8 seconds
window.SomApi.config.testServerEnabled = true;
window.SomApi.config.userInfoEnabled = true;
window.SomApi.config.latencyTestEnabled = true;
window.SomApi.config.uploadTestEnabled = true;
window.SomApi.config.progress.enabled = true; // fire onProgress during the test
window.SomApi.config.progress.verbose = true; // add currentSpeed and maxSpeed to it
The onTestCompleted callback receives a result object:
{
download: 150.5, // Download speed in Mbps
upload: 25.3, // Upload speed in Mbps ('' if uploadTestEnabled is false)
maxDownload: 162.0, // Peak download speed in Mbps
maxUpload: 27.1, // Peak upload speed in Mbps ('' if uploadTestEnabled is false)
latency: 12, // Latency in ms ('' if latencyTestEnabled is false)
jitter: 3, // Jitter in ms, whole number ('' if latencyTestEnabled is false)
testServer: 'Los Angeles 3', // Test server location ('' if testServerEnabled is false)
ip_address: '...', // Client IP ('0.0.0.0' if userInfoEnabled is false)
hostname: '...', // Client hostname ('' if userInfoEnabled is false)
userAgent: '...', // Browser user agent string
testDate: '...' // Test timestamp, ISO 8601
}
Every field is always present. Disabling a test blanks its fields rather than removing them.
The onProgress callback receives progress data:
{
type: 'download', // 'latency', 'download' or 'upload'
percentDone: 75, // 0-100
pass: 3, // Current test pass ('' during the latency phase)
currentSpeed: 145.2, // Current speed in Mbps, verbose mode only: '' unless
// config.progress.verbose is true, and always ''
// on latency events
maxSpeed: 162.0, // Peak speed in Mbps, same rule as currentSpeed
latency: '', // Latency in ms, only on the final latency event
// (type 'latency' at percentDone 100), '' otherwise
jitter: '' // Jitter in ms, same rule as latency
}
Guard currentSpeed before formatting it, because the latency phase
carries '':
const speed = typeof progress.currentSpeed === 'number'
? `${progress.currentSpeed.toFixed(1)} Mbps`
: '';
{
code: 1001,
message: 'User data error: Invalid Account'
}
Error codes:
1001: Invalid Account, API key not found or inactive1002: Domain Mismatch, the page domain does not match your registered domain2001: Test Error2002: Invalid server response2003: Request timeout2004: Test timeout, the test did not finish within config.testTimeout2005: Speed test could not start (status N)2006: Speed test engine did not loadCode 2006, and 2005 with status 0, mean the request never left the browser,
which is almost always an ad blocker, a content blocker or a privacy extension on the
visitor's device. The one thing to check on your own side is a frame-src
Content-Security-Policy: it must allow https://speedof.me, or the engine
iframe can never load. A 2005 carrying any other status is a real HTTP response, so treat
it as a server-side failure.
If you prefer the Options API:
<script>
export default {
data() {
return {
status: 'idle',
result: null,
progress: null,
error: null
};
},
mounted() {
window.SomApi.account = 'YOUR_API_KEY';
window.SomApi.domainName = 'your-domain.com';
window.SomApi.onTestCompleted = (result) => {
this.result = result;
this.status = 'completed';
};
window.SomApi.onProgress = (data) => {
this.progress = data;
};
window.SomApi.onError = (err) => {
this.error = err;
this.status = 'error';
};
},
methods: {
startTest() {
this.status = 'running';
window.SomApi.startTest();
}
}
};
</script>
For TypeScript projects, see the React example's TypeScript section for type definitions that work with Vue as well.
import.meta.env.VITE_API_KEY.onMounted to check if window.SomApi exists.