From 3ab5e39255b0d6af10e54e059a7bc29311cd21e7 Mon Sep 17 00:00:00 2001 From: peckz Date: Thu, 11 Sep 2025 16:54:56 +0200 Subject: [PATCH 1/2] feat: create speed test component --- package-lock.json | 122 +++++++++++- package.json | 1 + pages/utilities/internet-speed-test.tsx | 244 ++++++++++++++++++++++++ tailwind.config.ts | 9 + 4 files changed, 368 insertions(+), 8 deletions(-) create mode 100644 pages/utilities/internet-speed-test.tsx diff --git a/package-lock.json b/package-lock.json index 91b3505..8a7b5a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "jam-dev-utilities", "version": "0.1.0", "dependencies": { + "@cloudflare/speedtest": "^1.6.0", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", @@ -659,6 +660,19 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cloudflare/speedtest": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@cloudflare/speedtest/-/speedtest-1.6.0.tgz", + "integrity": "sha512-5EfTvWcDCAK6zOJpl7i4Ablzvxje7+dgVmhJxdK/uDuTIivyUVat/cCnxE67YYpuxKs+gbo569PbmHl+oI5eFA==", + "dependencies": { + "d3-scale": "^4.0.2", + "isomorphic-fetch": "^3.0.0", + "lodash.memoize": "^4.1.2" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -5278,6 +5292,81 @@ "node": ">=6" } }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7370,6 +7459,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -7842,6 +7939,15 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -9114,8 +9220,7 @@ "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -9511,7 +9616,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -9530,20 +9634,17 @@ "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -12389,6 +12490,11 @@ "node": ">=12" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", diff --git a/package.json b/package.json index 0f71bea..3d19d75 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "format:check": "prettier --check '**/*.{js,jsx,ts,tsx,json,css,scss,md}'" }, "dependencies": { + "@cloudflare/speedtest": "^1.6.0", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", diff --git a/pages/utilities/internet-speed-test.tsx b/pages/utilities/internet-speed-test.tsx new file mode 100644 index 0000000..5a5b890 --- /dev/null +++ b/pages/utilities/internet-speed-test.tsx @@ -0,0 +1,244 @@ +import { useCallback, useRef, useState } from "react"; +import PageHeader from "@/components/PageHeader"; +import Header from "@/components/Header"; +import { CMDK } from "@/components/CMDK"; +import CallToActionGrid from "@/components/CallToActionGrid"; +import Meta from "@/components/Meta"; +import SpeedTestEngine from "@cloudflare/speedtest"; +import { Card } from "@/components/ds/CardComponent"; +import { cn } from "@/lib/utils"; + +type TestState = { + status: "idle" | "running" | "finished"; + result: SpeedResult; +}; + +type SpeedResult = ReturnType< + typeof SpeedTestEngine.prototype.results.getSummary +>; + +const createSpeedTestEngine = () => { + return new SpeedTestEngine({ + autoStart: false, + measurements: [ + // Quick latency check (1-2 seconds) + { type: "latency", numPackets: 5 }, + + // Download test (4-5 seconds) + { type: "download", bytes: 1e6, count: 2, bypassMinDuration: true }, + { type: "download", bytes: 1e7, count: 1, bypassMinDuration: true }, + + // Upload test (4-5 seconds) + { type: "upload", bytes: 1e6, count: 2, bypassMinDuration: true }, + { type: "upload", bytes: 1e7, count: 1, bypassMinDuration: true }, + ], + }); +}; + +const outlineStyles = + "outline outline-2 outline-green-500 outline-offset-2 shadow-md transition"; + +export default function InternetSpeedTest() { + const [testState, setTestState] = useState({ + status: "idle", + result: {} as SpeedResult, + }); + + const engineRef = useRef(null); + + const toggleTest = useCallback(async () => { + if (testState.status === "running") { + engineRef.current?.pause?.(); + engineRef.current = null; + setTestState({ status: "idle", result: {} }); + return; + } + + setTestState({ status: "running", result: {} }); + const speedTest = createSpeedTestEngine(); + engineRef.current = speedTest; + + speedTest.onResultsChange = () => { + if (engineRef.current !== speedTest) { + return; + } + + setTestState((prev) => ({ + ...prev, + result: speedTest.results.getSummary(), + })); + }; + + speedTest.onFinish = (final) => { + if (engineRef.current !== speedTest) { + return; + } + + setTestState({ status: "finished", result: final.getSummary() }); + engineRef.current = null; + }; + + speedTest.onError = () => { + if (engineRef.current !== speedTest) { + return; + } + + engineRef.current = null; + setTestState({ status: "idle", result: {} }); + }; + + speedTest.play(); + }, [testState.status]); + + return ( +
+ +
+ + +
+ +
+ +
+
+ + + + + + + + + + + + { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggleTest(); + } + }} + > +
+
+ {getButtonLabel(testState.status)} +
+
+
+
+
+ + +
+ ); +} + +interface LabelProps { + isRunning: boolean; + title: string; +} + +const Label = (props: LabelProps) => { + return ( +
+

+ {props.title} +

+ {props.isRunning && } +
+ ); +}; + +const PulsatingCircle = () => { + return ( +
+ ); +}; + +const getButtonLabel = (status: TestState["status"]) => { + switch (status) { + case "idle": + return "START"; + case "running": + return "STOP"; + case "finished": + return "RESTART"; + default: + return "START"; + } +}; diff --git a/tailwind.config.ts b/tailwind.config.ts index 9129ddd..8f62c71 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -80,11 +80,20 @@ const config = { transform: "scale(1)", }, }, + "subtle-pulse": { + "0%, 100%": { + opacity: "1", + }, + "50%": { + opacity: ".5", + }, + }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", "grow-from-center": "grow-from-center 0.5s ease-out forwards", + "subtle-pulse": "subtle-pulse 1s ease-in-out infinite", }, }, }, From e28a80dcb53fe4fa6e5b29a2f977a88aadf98f52 Mon Sep 17 00:00:00 2001 From: peckz Date: Fri, 12 Sep 2025 16:19:18 +0200 Subject: [PATCH 2/2] feat: add SEO component for internet speed test --- README.md | 1 + components/seo/InternetSpeedTestSEO.tsx | 149 ++++++++++++++++++++++++ components/utils/tools-list.ts | 6 + pages/utilities/internet-speed-test.tsx | 5 + 4 files changed, 161 insertions(+) create mode 100644 components/seo/InternetSpeedTestSEO.tsx diff --git a/README.md b/README.md index 3698a1f..4e48155 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Here is the list of all utilities: - [Lorem Ipsum Generator](https://jam.dev/utilities/lorem-ipsum-generator) - [WebP Converter](https://jam.dev/utilities/webp-converter) - [SQL Minifer](https://jam.dev/utilities/sql-minifier) +- [Internet Speed Test]("https://jam.dev/utilities/internet-speed-test") ### Built With diff --git a/components/seo/InternetSpeedTestSEO.tsx b/components/seo/InternetSpeedTestSEO.tsx new file mode 100644 index 0000000..488d3f9 --- /dev/null +++ b/components/seo/InternetSpeedTestSEO.tsx @@ -0,0 +1,149 @@ +export default function InternetSpeedTestSEO() { + return ( +
+
+

+ Test your internet connection speed with our free, accurate internet + speed test tool. Measure your download and upload speeds, latency, and + connection quality instantly. Powered by Cloudflare's global network + for reliable results. +

+
+ +
+

How to Use the Internet Speed Test

+

+ Our speed test tool provides comprehensive internet connection + measurements using Cloudflare's edge network. Simply click the "Start" + button and wait for the test to complete - no signup or installation + required. +

+

+ The test measures multiple aspects of your connection including + download speed, upload speed, latency, and jitter. Results are + displayed in real-time. +

+
+ +
+

Understanding Your Speed Test Results

+

+ Internet speed tests measure several key metrics that affect your + online experience. Understanding these metrics helps you determine if + your connection meets your needs. +

+
    +
  • + Download Speed:
    + How fast data transfers from the internet to your device. Important + for streaming, downloading files, and web browsing. Measured in Mbps + (megabits per second). +
  • +
  • + Upload Speed:
    + How fast data transfers from your device to the internet. Critical + for video calls, uploading files, and live streaming. Usually slower + than download speed for most connections. +
  • +
  • + Latency (Ping):
    + The time it takes for data to travel to a server and back. Lower + latency means more responsive connections. Measured in milliseconds + (ms). Important for gaming and video calls. +
  • +
  • + Jitter:
    + The variation in latency over time. Lower jitter means more stable + connections. High jitter can cause issues with real-time + applications like gaming and video calls. +
  • +
+
+ +
+

Internet Speed Requirements

+

+ Different online activities require different internet speeds. Here's + what you need for common activities: +

+
    +
  • + Basic web browsing and email: 1-5 Mbps +
  • +
  • + HD video streaming: 5-25 Mbps +
  • +
  • + 4K video streaming: 25-50 Mbps +
  • +
  • + Online gaming: 3-6 Mbps (with low latency) +
  • +
  • + Video calls: 1-8 Mbps +
  • +
  • + Large file downloads: 50+ Mbps +
  • +
+
+ +
+

Why Use Cloudflare Speed Test?

+

+ Our speed test uses Cloudflare's global network, which provides more + accurate results than traditional speed tests. Cloudflare has servers + in over 200 cities worldwide, so you're likely testing against a + server close to your location. +

+

+ The test is performed directly in your browser using modern web APIs, + ensuring accurate measurements of your actual browsing experience. No + plugins or software installations required. +

+
+ +
+

FAQs

+
    +
  • + How accurate is this speed test?
    + Our speed test uses Cloudflare's global CDN network, providing + highly accurate results by testing against servers close to your + location. Results reflect real-world browsing performance. +
  • +
  • + Why might my speed test results vary?
    + Internet speeds can vary due to network congestion, time of day, + your device's performance, background applications, and your ISP's + current load. Run multiple tests for the most accurate average. +
  • +
  • + Should I close other applications before testing?
    + For the most accurate results, close applications that use the + internet (streaming, downloads, cloud sync) during the test. This + ensures the test measures your connection's full capacity. +
  • +
  • + What's a good internet speed?
    + It depends on your usage. For basic browsing: 5-10 Mbps. For HD + streaming: 25+ Mbps. For multiple users or 4K streaming: 50+ Mbps. + For heavy usage or multiple devices: 100+ Mbps. +
  • +
  • + Why is my upload speed slower than download?
    + Most internet plans are asymmetric, providing faster download than + upload speeds. This is because most users download more data + (streaming, browsing) than they upload. +
  • +
  • + Is this speed test free?
    + Yes, completely free with no registration required. No ads, no + tracking, and open source. Just like all our developer utilities at + Jam. +
  • +
+
+
+ ); +} diff --git a/components/utils/tools-list.ts b/components/utils/tools-list.ts index ef41923..fe66855 100644 --- a/components/utils/tools-list.ts +++ b/components/utils/tools-list.ts @@ -149,4 +149,10 @@ export const tools = [ "Minify SQL by removing comments, extra spaces, and formatting for cleaner, optimized queries.", link: "/utilities/sql-minifier", }, + { + title: "Internet Speed Test", + description: + "Test your internet connection speed with accurate measurements by using Cloudflare's global network.", + link: "/utilities/internet-speed-test", + }, ]; diff --git a/pages/utilities/internet-speed-test.tsx b/pages/utilities/internet-speed-test.tsx index 5a5b890..e09d0ef 100644 --- a/pages/utilities/internet-speed-test.tsx +++ b/pages/utilities/internet-speed-test.tsx @@ -7,6 +7,7 @@ import Meta from "@/components/Meta"; import SpeedTestEngine from "@cloudflare/speedtest"; import { Card } from "@/components/ds/CardComponent"; import { cn } from "@/lib/utils"; +import InternetSpeedTestSEO from "@/components/seo/InternetSpeedTestSEO"; type TestState = { status: "idle" | "running" | "finished"; @@ -204,6 +205,10 @@ export default function InternetSpeedTest() { + +
+ +
); }