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 <title> and <meta description>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
emil
2026-05-09 14:54:29 +03:00
co-authored by Claude Sonnet 4.6
parent af64203e17
commit 80a39035d3
14 changed files with 129 additions and 19 deletions
+3
View File
@@ -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()],
},
+82
View File
@@ -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",
+2 -1
View File
@@ -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"
}
}
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://randify.pro/sitemap-index.xml
+20
View File
@@ -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.',
},
];
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">
+2 -2
View File
@@ -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">