From 80a39035d3f837b4678d54fec502a1d83800e3d4 Mon Sep 17 00:00:00 2001 From: emil Date: Sat, 9 May 2026 14:54:29 +0300 Subject: [PATCH] seo: add sitemap, robots.txt and optimised page titles/descriptions - @astrojs/sitemap generates sitemap-index.xml at build time - robots.txt points crawlers to the sitemap - generators.ts extended with seoTitle and seoDescription per generator - All generator pages now use seoTitle/seoDescription for and <meta description> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- astro.config.mjs | 3 ++ package-lock.json | 82 +++++++++++++++++++++++++++++ package.json | 3 +- public/robots.txt | 4 ++ src/data/generators.ts | 20 +++++++ src/pages/generators/cards.astro | 4 +- src/pages/generators/coin.astro | 4 +- src/pages/generators/colors.astro | 4 +- src/pages/generators/dice.astro | 4 +- src/pages/generators/list.astro | 4 +- src/pages/generators/lottery.astro | 4 +- src/pages/generators/numbers.astro | 4 +- src/pages/generators/password.astro | 4 +- src/pages/generators/uuid.astro | 4 +- 14 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 public/robots.txt diff --git a/astro.config.mjs b/astro.config.mjs index 2b45c70..518a3c2 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,7 +1,10 @@ import { defineConfig } from 'astro/config'; import tailwindcss from '@tailwindcss/vite'; +import sitemap from '@astrojs/sitemap'; export default defineConfig({ + site: 'https://randify.pro', + integrations: [sitemap()], vite: { plugins: [tailwindcss()], }, diff --git a/package-lock.json b/package-lock.json index 676afb0..c590151 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "randify", "version": "0.0.1", "dependencies": { + "@astrojs/sitemap": "^3.2.1", "@tailwindcss/vite": "^4.0.0", "astro": "^4.16.0", "tailwindcss": "^4.0.0" @@ -59,6 +60,17 @@ "node": "^18.17.1 || ^20.3.0 || >=21.0.0" } }, + "node_modules/@astrojs/sitemap": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.2.1.tgz", + "integrity": "sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^3.23.8" + } + }, "node_modules/@astrojs/telemetry": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.1.0.tgz", @@ -1795,6 +1807,24 @@ "@types/unist": "*" } }, + "node_modules/@types/node": { + "version": "24.12.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.3.tgz", + "integrity": "sha512-8oljBDGun9cIsZRJR6fkihn0TSXJI0UDOOhncYaERq6M0JMDoPLxyscwruJcb4GKS6dvK/d8xebYBg27h/duaQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -1883,6 +1913,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5315,6 +5351,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -5417,6 +5462,31 @@ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, + "node_modules/sitemap": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.3.tgz", + "integrity": "sha512-9Ew1tR2WYw8RGE2XLy7GjkusvYXy8Rg6y8TYuBuQMfIEdGcWoJpY2Wr5DzsEiL/TKCw56+YKTCCUHglorEYK+A==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5450,6 +5520,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -5625,6 +5701,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", diff --git a/package.json b/package.json index fb80cc2..8414a65 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ "preview": "astro preview" }, "dependencies": { - "astro": "^4.16.0", + "@astrojs/sitemap": "^3.2.1", "@tailwindcss/vite": "^4.0.0", + "astro": "^4.16.0", "tailwindcss": "^4.0.0" } } diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..2096197 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://randify.pro/sitemap-index.xml diff --git a/src/data/generators.ts b/src/data/generators.ts index 257eda7..7175e3b 100644 --- a/src/data/generators.ts +++ b/src/data/generators.ts @@ -4,6 +4,8 @@ export interface Generator { description: string; icon: string; status: 'live' | 'coming-soon'; + seoTitle: string; + seoDescription: string; } export const generators: Generator[] = [ @@ -13,6 +15,8 @@ export const generators: Generator[] = [ description: 'Pick a random number within any range you choose.', icon: 'hash', status: 'live', + seoTitle: 'Random Number Generator | Randify', + seoDescription: 'Generate a random number between any two values instantly. Free online random number picker — perfect for giveaways, games, and decisions.', }, { slug: 'colors', @@ -20,6 +24,8 @@ export const generators: Generator[] = [ description: 'Generate a random color in HEX, RGB, or HSL format.', icon: 'palette', status: 'live', + seoTitle: 'Random Color Generator — HEX, RGB, HSL | Randify', + seoDescription: 'Get a random color in HEX, RGB or HSL format with one click. Free online color randomizer for designers and developers.', }, { slug: 'password', @@ -27,6 +33,8 @@ export const generators: Generator[] = [ description: 'Create a strong, random password with custom rules.', icon: 'lock', status: 'live', + seoTitle: 'Random Password Generator | Randify', + seoDescription: 'Create a strong random password with custom length, uppercase, lowercase, digits and symbols. Free and secure — runs entirely in your browser.', }, { slug: 'lottery', @@ -34,6 +42,8 @@ export const generators: Generator[] = [ description: 'Draw a set of unique numbers for lottery-style picks.', icon: 'ticket', status: 'live', + seoTitle: 'Lottery Number Generator — Random Pick | Randify', + seoDescription: 'Draw a set of unique random lottery numbers from any range. Perfect for lotteries, raffles, and lucky draws. Free online lottery picker.', }, { slug: 'dice', @@ -41,6 +51,8 @@ export const generators: Generator[] = [ description: 'Roll one or more dice with any number of sides.', icon: 'dice-6', status: 'live', + seoTitle: 'Online Dice Roller — d4, d6, d20 and more | Randify', + seoDescription: 'Roll virtual dice online. Supports d4, d6, d8, d10, d12, d20 and d100. Roll multiple dice at once — great for D&D, board games and tabletop RPGs.', }, { slug: 'cards', @@ -48,6 +60,8 @@ export const generators: Generator[] = [ description: 'Draw a random playing card from a standard deck.', icon: 'square-stack', status: 'live', + seoTitle: 'Random Card Generator — Draw from a Deck | Randify', + seoDescription: 'Draw random playing cards from a standard 52-card deck. Pick any number of cards, with or without replacement. Free online card picker.', }, { slug: 'coin', @@ -55,6 +69,8 @@ export const generators: Generator[] = [ description: 'Flip one or more coins and see heads or tails.', icon: 'circle-dollar-sign', status: 'live', + seoTitle: 'Coin Flip Online — Heads or Tails | Randify', + seoDescription: 'Flip a virtual coin online. Flip multiple coins at once and see how many heads and tails you get. Free random coin toss simulator.', }, { slug: 'list', @@ -62,6 +78,8 @@ export const generators: Generator[] = [ description: 'Paste a list of items and pick random winners.', icon: 'list', status: 'live', + seoTitle: 'Random List Picker — Pick a Random Winner | Randify', + seoDescription: 'Paste any list of names or items and pick random winners instantly. Great for giveaways, choosing who goes first, or any random selection.', }, { slug: 'uuid', @@ -69,5 +87,7 @@ export const generators: Generator[] = [ description: 'Generate UUIDs, hex tokens, or random base64 strings.', icon: 'fingerprint', status: 'live', + seoTitle: 'UUID Generator — UUID v4, Hex & Base64 Tokens | Randify', + seoDescription: 'Generate random UUID v4, hex tokens, or base64 strings online. Cryptographically secure, runs in your browser. Free UUID and token generator.', }, ]; diff --git a/src/pages/generators/cards.astro b/src/pages/generators/cards.astro index 7f35616..1940cac 100644 --- a/src/pages/generators/cards.astro +++ b/src/pages/generators/cards.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'cards')!; --- <BaseLayout - title={`${generator.title} Generator — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/coin.astro b/src/pages/generators/coin.astro index 6a4549d..4283f34 100644 --- a/src/pages/generators/coin.astro +++ b/src/pages/generators/coin.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'coin')!; --- <BaseLayout - title={`${generator.title} — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/colors.astro b/src/pages/generators/colors.astro index db7fe7b..4811afe 100644 --- a/src/pages/generators/colors.astro +++ b/src/pages/generators/colors.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'colors')!; --- <BaseLayout - title={`${generator.title} Generator — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/dice.astro b/src/pages/generators/dice.astro index 0214f07..327c527 100644 --- a/src/pages/generators/dice.astro +++ b/src/pages/generators/dice.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'dice')!; --- <BaseLayout - title={`${generator.title} Generator — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/list.astro b/src/pages/generators/list.astro index c80c31a..6aa409d 100644 --- a/src/pages/generators/list.astro +++ b/src/pages/generators/list.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'list')!; --- <BaseLayout - title={`${generator.title} — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/lottery.astro b/src/pages/generators/lottery.astro index 3d87ea8..3b091d4 100644 --- a/src/pages/generators/lottery.astro +++ b/src/pages/generators/lottery.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'lottery')!; --- <BaseLayout - title={`${generator.title} Generator — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/numbers.astro b/src/pages/generators/numbers.astro index cac09f2..17a2a03 100644 --- a/src/pages/generators/numbers.astro +++ b/src/pages/generators/numbers.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'numbers')!; --- <BaseLayout - title={`${generator.title} Generator — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/password.astro b/src/pages/generators/password.astro index 0364387..2ff91bf 100644 --- a/src/pages/generators/password.astro +++ b/src/pages/generators/password.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'password')!; --- <BaseLayout - title={`${generator.title} Generator — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10"> diff --git a/src/pages/generators/uuid.astro b/src/pages/generators/uuid.astro index 5de87ef..2c7f501 100644 --- a/src/pages/generators/uuid.astro +++ b/src/pages/generators/uuid.astro @@ -8,8 +8,8 @@ const generator = generators.find((g) => g.slug === 'uuid')!; --- <BaseLayout - title={`${generator.title} — Randify`} - description={generator.description} + title={generator.seoTitle} + description={generator.seoDescription} > <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20"> <nav aria-label="Breadcrumb" class="mb-10">