seo
This commit is contained in:
@@ -3,11 +3,12 @@ import { defineConfig } from 'astro/config';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://mintel.me',
|
||||
integrations: [react(), mdx()],
|
||||
integrations: [react(), mdx(), sitemap()],
|
||||
markdown: {
|
||||
syntaxHighlight: 'prism'
|
||||
}
|
||||
|
||||
259
package-lock.json
generated
259
package-lock.json
generated
@@ -10,10 +10,12 @@
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.13",
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@astrojs/sitemap": "^3.6.1",
|
||||
"@astrojs/tailwind": "^6.0.2",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vercel/og": "^0.8.6",
|
||||
"astro": "^5.16.8",
|
||||
"ioredis": "^5.9.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
@@ -250,6 +252,17 @@
|
||||
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/sitemap": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.6.1.tgz",
|
||||
"integrity": "sha512-+o+TbxXqQJAOd+HxCjz/5RdAMrRFGjeuO+U6zddUuTO59WqMqXnsc8uveRiEr2Ff+3McZiEne7iG4J5cnuI6kA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sitemap": "^8.0.2",
|
||||
"stream-replace-string": "^2.0.0",
|
||||
"zod": "^3.25.76"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/tailwind": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-6.0.2.tgz",
|
||||
@@ -1641,6 +1654,15 @@
|
||||
"integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@resvg/resvg-wasm": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.4.0.tgz",
|
||||
"integrity": "sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==",
|
||||
"license": "MPL-2.0",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
@@ -2069,6 +2091,22 @@
|
||||
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@shuding/opentype.js": {
|
||||
"version": "1.4.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
|
||||
"integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fflate": "^0.7.3",
|
||||
"string.prototype.codepointat": "^0.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"ot": "bin/ot"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
@@ -2229,6 +2267,15 @@
|
||||
"@types/react": "^19.2.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",
|
||||
@@ -2241,6 +2288,19 @@
|
||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vercel/og": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.8.6.tgz",
|
||||
"integrity": "sha512-hBcWIOppZV14bi+eAmCZj8Elj8hVSUZJTpf1lgGBhVD85pervzQ1poM/qYfFUlPraYSZYP+ASg6To5BwYmUSGQ==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@resvg/resvg-wasm": "2.4.0",
|
||||
"satori": "0.16.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||
@@ -2680,6 +2740,15 @@
|
||||
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
||||
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.14",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
|
||||
@@ -2795,6 +2864,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001764",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
|
||||
@@ -2947,6 +3025,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||
@@ -3006,6 +3090,36 @@
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/css-background-parser": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
|
||||
"integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-box-shadow": {
|
||||
"version": "1.0.0-3",
|
||||
"resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
|
||||
"integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-gradient-parser": {
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.16.tgz",
|
||||
"integrity": "sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||
@@ -3022,6 +3136,17 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
||||
@@ -3414,6 +3539,12 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
@@ -3583,6 +3714,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
|
||||
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -3974,6 +4111,18 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hex-rgb": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
|
||||
"integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||
@@ -4287,6 +4436,16 @@
|
||||
"url": "https://github.com/sponsors/antonk52"
|
||||
}
|
||||
},
|
||||
"node_modules/linebreak": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
|
||||
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "0.0.8",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
@@ -5637,6 +5796,22 @@
|
||||
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parse-css-color": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
|
||||
"integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.1.4",
|
||||
"hex-rgb": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-entities": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
|
||||
@@ -6472,6 +6647,37 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/satori": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/satori/-/satori-0.16.0.tgz",
|
||||
"integrity": "sha512-ZvHN3ygzZ8FuxjSNB+mKBiF/NIoqHzlBGbD0MJiT+MvSsFOvotnWOhdTjxKzhHRT2wPC1QbhLzx2q/Y83VhfYQ==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@shuding/opentype.js": "1.4.0-beta.0",
|
||||
"css-background-parser": "^0.1.0",
|
||||
"css-box-shadow": "1.0.0-3",
|
||||
"css-gradient-parser": "^0.0.16",
|
||||
"css-to-react-native": "^3.0.0",
|
||||
"emoji-regex-xs": "^2.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"linebreak": "^1.1.0",
|
||||
"parse-css-color": "^0.2.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"yoga-layout": "^3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/satori/node_modules/emoji-regex-xs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-2.0.1.tgz",
|
||||
"integrity": "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
||||
@@ -6566,6 +6772,31 @@
|
||||
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sitemap": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz",
|
||||
"integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==",
|
||||
"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/smol-toml": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz",
|
||||
@@ -6612,6 +6843,12 @@
|
||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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",
|
||||
@@ -6629,6 +6866,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.codepointat": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
|
||||
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stringify-entities": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
||||
@@ -7525,6 +7768,16 @@
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unified": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||
@@ -8084,6 +8337,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yoga-layout": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
|
||||
"integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.13",
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@astrojs/sitemap": "^3.6.1",
|
||||
"@astrojs/tailwind": "^6.0.2",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vercel/og": "^0.8.6",
|
||||
"astro": "^5.16.8",
|
||||
"ioredis": "^5.9.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
|
||||
4
public/robots.txt
Normal file
4
public/robots.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://mintel.me/sitemap.xml
|
||||
@@ -784,15 +784,15 @@ async function runAllTests() {
|
||||
() => test('Download API endpoint exists', () => {
|
||||
const apiPath = path.join(process.cwd(), 'src/pages/api/download-zip.ts');
|
||||
const content = fs.readFileSync(apiPath, 'utf8');
|
||||
|
||||
|
||||
if (!content.includes('export const POST')) {
|
||||
throw new Error('API endpoint missing POST handler');
|
||||
}
|
||||
|
||||
|
||||
if (!content.includes('export const GET')) {
|
||||
throw new Error('API endpoint missing GET handler');
|
||||
}
|
||||
|
||||
|
||||
if (!content.includes('FileExampleManager')) {
|
||||
throw new Error('API endpoint does not use FileExampleManager');
|
||||
}
|
||||
@@ -883,21 +883,104 @@ async function runAllTests() {
|
||||
'src/components/FileExample.astro',
|
||||
'src/components/FileExamplesList.astro'
|
||||
];
|
||||
|
||||
|
||||
for (const component of components) {
|
||||
const componentPath = path.join(process.cwd(), component);
|
||||
if (!fs.existsSync(componentPath)) {
|
||||
throw new Error(`Component ${component} does not exist`);
|
||||
}
|
||||
|
||||
|
||||
const content = fs.readFileSync(componentPath, 'utf8');
|
||||
|
||||
|
||||
// Check for common issues
|
||||
if (content.includes('children.trim') && !content.includes('children?.trim')) {
|
||||
throw new Error(`${component}: uses children.trim without null check`);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
() => test('OG image API route exists', () => {
|
||||
const ogApiPath = path.join(process.cwd(), 'src/pages/api/og/[...slug].svg.ts');
|
||||
|
||||
if (!fs.existsSync(ogApiPath)) {
|
||||
throw new Error('OG image API route does not exist');
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(ogApiPath, 'utf8');
|
||||
|
||||
if (!content.includes('export async function getStaticPaths')) {
|
||||
throw new Error('OG API route missing getStaticPaths function');
|
||||
}
|
||||
|
||||
if (!content.includes('export const GET')) {
|
||||
throw new Error('OG API route missing GET handler');
|
||||
}
|
||||
|
||||
if (!content.includes('blogPosts')) {
|
||||
throw new Error('OG API route does not use blogPosts data');
|
||||
}
|
||||
}),
|
||||
|
||||
() => test('OG images are generated in dist', () => {
|
||||
const ogDir = path.join(process.cwd(), 'dist/api/og');
|
||||
|
||||
if (!fs.existsSync(ogDir)) {
|
||||
throw new Error('OG images directory does not exist in dist');
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(ogDir);
|
||||
const expectedFiles = ['home.svg', 'first-note.svg', 'debugging-tips.svg', 'architecture-patterns.svg', 'docker-deployment.svg', 'embed-demo.svg'];
|
||||
|
||||
for (const expectedFile of expectedFiles) {
|
||||
if (!files.includes(expectedFile)) {
|
||||
throw new Error(`Missing OG image: ${expectedFile}`);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
() => test('OG images have correct content', () => {
|
||||
const ogDir = path.join(process.cwd(), 'dist/api/og');
|
||||
const homeOgPath = path.join(ogDir, 'home.svg');
|
||||
|
||||
if (!fs.existsSync(homeOgPath)) {
|
||||
throw new Error('Home OG image does not exist');
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(homeOgPath, 'utf8');
|
||||
|
||||
if (!content.includes('<svg')) {
|
||||
throw new Error('OG image is not valid SVG');
|
||||
}
|
||||
|
||||
if (!content.includes('Marc Mintel')) {
|
||||
throw new Error('OG image does not contain expected title');
|
||||
}
|
||||
|
||||
if (!content.includes('mintel.me')) {
|
||||
throw new Error('OG image does not contain site branding');
|
||||
}
|
||||
|
||||
if (!content.includes('system-ui')) {
|
||||
throw new Error('OG image does not use system fonts');
|
||||
}
|
||||
}),
|
||||
|
||||
() => test('BaseLayout uses OG images', () => {
|
||||
const baseLayoutPath = path.join(process.cwd(), 'src/layouts/BaseLayout.astro');
|
||||
const content = fs.readFileSync(baseLayoutPath, 'utf8');
|
||||
|
||||
if (!content.includes('ogImage')) {
|
||||
throw new Error('BaseLayout does not set og:image meta tag');
|
||||
}
|
||||
|
||||
if (!content.includes('twitterImage')) {
|
||||
throw new Error('BaseLayout does not set twitter:image meta tag');
|
||||
}
|
||||
|
||||
if (!content.includes('/api/og/')) {
|
||||
throw new Error('BaseLayout does not use OG image API route');
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
// Run all sync tests
|
||||
|
||||
@@ -22,9 +22,19 @@ import 'prismjs/components/prism-markdown';
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
keywords?: string[];
|
||||
canonicalUrl?: string;
|
||||
}
|
||||
|
||||
const { title, description = "Technical problem solver's blog - practical insights and learning notes" } = Astro.props;
|
||||
const { title, description = "Technical problem solver's blog - practical insights and learning notes", image, keywords, canonicalUrl } = Astro.props;
|
||||
const siteUrl = 'https://mintel.me';
|
||||
const currentUrl = canonicalUrl || (new URL(Astro.request.url)).pathname;
|
||||
const fullUrl = `${siteUrl}${currentUrl}`;
|
||||
const slug = currentUrl.split('/').filter(Boolean).pop() || 'home';
|
||||
const ogImage = image || `${siteUrl}/api/og/${slug}.svg`;
|
||||
const twitterImage = image || `${siteUrl}/api/og/${slug}.svg`;
|
||||
const keywordsString = keywords ? keywords.join(', ') : '';
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
@@ -34,7 +44,24 @@ const { title, description = "Technical problem solver's blog - practical insigh
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{title} | Marc Mintel</title>
|
||||
<meta name="description" content={description} />
|
||||
{keywordsString && <meta name="keywords" content={keywordsString} />}
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<link rel="canonical" href={fullUrl} />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={fullUrl} />
|
||||
<meta property="og:title" content={`${title} | Marc Mintel`} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={ogImage} />
|
||||
<meta property="og:site_name" content="Marc Mintel" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={fullUrl} />
|
||||
<meta property="twitter:title" content={`${title} | Marc Mintel`} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={twitterImage} />
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
|
||||
72
src/pages/api/og/[...slug].svg.ts
Normal file
72
src/pages/api/og/[...slug].svg.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { blogPosts } from '../../../data/blogPosts';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const paths = blogPosts.map(post => ({
|
||||
params: { slug: post.slug }
|
||||
}));
|
||||
|
||||
// Add home page
|
||||
paths.push({ params: { slug: 'home' } });
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async ({ params }) => {
|
||||
const slug = params.slug;
|
||||
|
||||
let title: string;
|
||||
let description: string;
|
||||
|
||||
// Handle home page
|
||||
if (slug === 'home') {
|
||||
title = 'Marc Mintel';
|
||||
description = 'Technical problem solver\'s blog - practical insights and learning notes';
|
||||
} else {
|
||||
// Find the blog post
|
||||
const post = blogPosts.find(p => p.slug === slug);
|
||||
|
||||
// Default content if no post found
|
||||
title = (post?.title || 'Marc Mintel').replace(/[<>&'"]/g, '');
|
||||
description = (post?.description || 'Technical problem solver\'s blog - practical insights and learning notes').slice(0, 100).replace(/[<>&'"]/g, '');
|
||||
}
|
||||
|
||||
// Create SVG with typographic design matching the site
|
||||
const svg = `<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>
|
||||
.title { font-family: system-ui, -apple-system, sans-serif; font-size: 48px; font-weight: 700; fill: #1e293b; letter-spacing: -0.025em; }
|
||||
.description { font-family: system-ui, -apple-system, sans-serif; font-size: 24px; font-weight: 400; fill: #64748b; }
|
||||
.branding { font-family: system-ui, -apple-system, sans-serif; font-size: 18px; font-weight: 500; fill: #94a3b8; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="1200" height="630" fill="#ffffff"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="60" y="200" class="title">
|
||||
${title}
|
||||
</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="60" y="280" class="description">
|
||||
${description}
|
||||
</text>
|
||||
|
||||
<!-- Site branding -->
|
||||
<text x="60" y="580" class="branding">
|
||||
mintel.me
|
||||
</text>
|
||||
|
||||
<!-- Decorative accent -->
|
||||
<rect x="1000" y="60" width="120" height="4" fill="#3b82f6" rx="2"/>
|
||||
</svg>`;
|
||||
|
||||
return new Response(svg, {
|
||||
headers: {
|
||||
'Content-Type': 'image/svg+xml',
|
||||
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -33,7 +33,12 @@ const showFileExamples = post.tags?.some(tag =>
|
||||
);
|
||||
---
|
||||
|
||||
<BaseLayout title={post.title} description={post.description}>
|
||||
<BaseLayout
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
keywords={post.tags}
|
||||
canonicalUrl={`/blog/${post.slug}`}
|
||||
/>
|
||||
<!-- Top navigation bar with back button and clap counter -->
|
||||
<nav class="fixed top-0 left-0 right-0 z-40 bg-white/80 backdrop-blur-md border-b border-slate-200 transition-all duration-300" id="top-nav">
|
||||
<div class="max-w-4xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
@@ -266,6 +271,33 @@ const showFileExamples = post.tags?.some(tag =>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Structured Data for SEO -->
|
||||
<script type="application/ld+json" set:html={JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": post.title,
|
||||
"description": post.description,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Marc Mintel",
|
||||
"url": "https://mintel.me"
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Person",
|
||||
"name": "Marc Mintel",
|
||||
"url": "https://mintel.me"
|
||||
},
|
||||
"datePublished": post.date,
|
||||
"dateModified": post.date,
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": `https://mintel.me/blog/${post.slug}`
|
||||
},
|
||||
"url": `https://mintel.me/blog/${post.slug}`,
|
||||
"keywords": post.tags.join(", "),
|
||||
"articleSection": post.tags[0] || "Technology"
|
||||
})} />
|
||||
|
||||
<script>
|
||||
// Reading progress bar with smooth gradient
|
||||
function updateReadingProgress() {
|
||||
|
||||
Reference in New Issue
Block a user