From 8d37a4e37c4f8003b2d0a7e9c724264373a02386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=A7al=20Albert=20Castellv=C3=AD?= Date: Sun, 26 Jul 2026 21:05:19 +0200 Subject: [PATCH 01/12] fix(website): improve crawlability and indexing Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/iac/lib/stacks/cloudfront-url-rewrite.js | 56 +++++++-- src/iac/lib/stacks/staticWebsiteStack.ts | 8 +- src/website/public/llms.txt | 28 +++++ src/website/public/robots.txt | 4 + src/website/src/i18n/ca.ts | 19 ++- src/website/src/i18n/en.ts | 19 ++- src/website/src/i18n/es.ts | 19 ++- src/website/src/i18n/types.ts | 11 ++ src/website/src/layouts/BaseLayout.astro | 79 +++++++++++- src/website/src/pages/404.astro | 60 +++++++++ src/website/src/pages/ca/changelog.astro | 1 + src/website/src/pages/ca/docs.astro | 1 + src/website/src/pages/changelog.astro | 1 + src/website/src/pages/docs.astro | 1 + src/website/src/pages/es/changelog.astro | 1 + src/website/src/pages/es/docs.astro | 1 + .../staticWebsiteStack.test.ts.snap | 66 +++++++--- .../lib/stacks/cloudfrontUrlRewrite.test.ts | 118 +++++++++++++++--- .../iac/lib/stacks/staticWebsiteStack.test.ts | 33 ++++- 19 files changed, 463 insertions(+), 63 deletions(-) create mode 100644 src/website/public/llms.txt create mode 100644 src/website/public/robots.txt create mode 100644 src/website/src/pages/404.astro diff --git a/src/iac/lib/stacks/cloudfront-url-rewrite.js b/src/iac/lib/stacks/cloudfront-url-rewrite.js index 58063c83..1e2c83ac 100644 --- a/src/iac/lib/stacks/cloudfront-url-rewrite.js +++ b/src/iac/lib/stacks/cloudfront-url-rewrite.js @@ -1,7 +1,3 @@ -// CloudFront Function for URL rewriting -// Compatible with CloudFront Functions runtime (ES5.1) -// Query strings are automatically preserved by CloudFront - function handler(event) { var req = event.request; var uri = req.uri; @@ -48,22 +44,56 @@ function handler(event) { return false; } + function getQueryString(querystring) { + if (typeof querystring === "string") { + return querystring ? "?" + querystring : ""; + } + + var parts = []; + for (var key in querystring) { + if (Object.prototype.hasOwnProperty.call(querystring, key)) { + var parameter = querystring[key]; + var values = parameter.multiValue || [parameter]; + + for (var i = 0; i < values.length; i++) { + var value = values[i].value; + parts.push(key + (value ? "=" + value : "")); + } + } + } + + return parts.length ? "?" + parts.join("&") : ""; + } + + function redirect(path) { + return { + statusCode: 301, + statusDescription: "Moved Permanently", + headers: { + location: { + value: path + getQueryString(req.querystring), + }, + }, + }; + } + + if (uri === "/sitemap.xml") { + return redirect("/sitemap-index.xml"); + } + // Exclude API routes var lower = uri.toLowerCase(); if (hasPrefix(lower, "/api/")) { return req; // no changes } - // Remove trailing slash (except homepage) - if (uri !== "/" && endsWith(uri, "/")) { - uri = uri.substring(0, uri.length - 1); - lower = uri.toLowerCase(); // recompute after modifying uri - } - - // Append /index.html if not homepage and no known extension - // Astro generates directory-based URLs: /docs → /docs/index.html + // Astro generates directory-based URLs with trailing slashes. if (uri !== "/" && !hasKnownExt(lower)) { - uri += "/index.html"; + if (!endsWith(uri, "/")) { + return redirect(uri + "/"); + } + + uri += "index.html"; } req.uri = uri; diff --git a/src/iac/lib/stacks/staticWebsiteStack.ts b/src/iac/lib/stacks/staticWebsiteStack.ts index 0b011df1..ef979a4d 100644 --- a/src/iac/lib/stacks/staticWebsiteStack.ts +++ b/src/iac/lib/stacks/staticWebsiteStack.ts @@ -134,15 +134,15 @@ export class StaticWebsiteStack extends CustomStack { const errorResponse403: ErrorResponse = { httpStatus: 403, - responseHttpStatus: 200, - responsePagePath: "/index.html", + responseHttpStatus: 404, + responsePagePath: "/404.html", ttl: Duration.seconds(10), }; const errorResponse404: ErrorResponse = { httpStatus: 404, - responseHttpStatus: 200, - responsePagePath: "/index.html", + responseHttpStatus: 404, + responsePagePath: "/404.html", ttl: Duration.seconds(10), }; diff --git a/src/website/public/llms.txt b/src/website/public/llms.txt new file mode 100644 index 00000000..9f5345b4 --- /dev/null +++ b/src/website/public/llms.txt @@ -0,0 +1,28 @@ +# Envilder + +> Envilder is an open-source secret resolution tool for applications that use AWS SSM Parameter Store or Azure Key Vault. + +## What Envilder does + +Envilder keeps a version-controlled JSON mapping between environment-variable names and secret paths. It resolves that mapping consistently during local development, CI/CD, and application startup. + +- Envilder does not store or proxy secret values. +- Secrets remain in AWS SSM Parameter Store or Azure Key Vault. +- The CLI supports pull and push workflows. +- The GitHub Action supports pull workflows for CI/CD. +- Runtime SDKs are available for .NET, Python, and Node.js. + +## Official resources + +- Product overview: https://envilder.com/ +- Documentation: https://envilder.com/docs/ +- Changelog: https://envilder.com/changelog/ +- Source repository: https://github.com/macalbert/envilder +- Mapping schema: https://envilder.com/schema/map-file.v1.json + +## Product terminology + +- `envilder.json`: the JSON mapping file that defines the environment contract. +- AWS SSM Parameter Store: a supported secret provider. +- Azure Key Vault: a supported secret provider. +- Runtime SDK: a library that resolves mapped secrets directly in an application process. diff --git a/src/website/public/robots.txt b/src/website/public/robots.txt new file mode 100644 index 00000000..bd7c254f --- /dev/null +++ b/src/website/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://envilder.com/sitemap-index.xml diff --git a/src/website/src/i18n/ca.ts b/src/website/src/i18n/ca.ts index 72257e2b..d4398e73 100644 --- a/src/website/src/i18n/ca.ts +++ b/src/website/src/i18n/ca.ts @@ -3,9 +3,16 @@ import type { Translations } from './types'; export const ca: Translations = { homeMeta: { title: - 'Envilder: estandarditza com les teves aplicacions consumeixen secrets a cada entorn i runtime.', + 'Gestió de secrets open source per a AWS SSM i Azure Key Vault | Envilder', description: - "Defineix el contracte d'entorn una vegada i resol-lo de forma consistent en desenvolupament local, CI/CD i runtime. Amb AWS SSM Parameter Store i Azure Key Vault.", + "Envilder és una CLI, GitHub Action i SDK de runtime open source per carregar secrets des d'AWS SSM Parameter Store i Azure Key Vault.", + }, + notFound: { + title: 'Pàgina no trobada | Envilder', + description: "La pàgina d'Envilder sol·licitada no existeix.", + heading: 'No hem trobat aquesta pàgina', + body: "L'adreça pot ser incorrecta o la pàgina pot haver-se mogut.", + home: 'Torna a l’inici', }, nav: { features: 'Funcionalitats', @@ -451,7 +458,9 @@ export const ca: Translations = { builtWith: 'Fet amb Astro. Codi obert a GitHub.', }, changelogPage: { - title: 'Changelog Envilder | Versions i actualitzacions', + title: 'Canvis d’Envilder: versions de la CLI, GitHub Action i SDKs', + description: + "Historial de versions de la CLI, GitHub Action i SDKs de runtime .NET, Python i Node.js d'Envilder.", backToHome: "← Tornar a l'inici", fullChangelog: 'Historial de ', @@ -468,7 +477,9 @@ export const ca: Translations = { categorySdkNodejs: 'Node.js', }, docs: { - title: 'Docs Envilder | CLI, GitHub Action i AWS SSM', + title: 'Docs Envilder: secrets amb AWS SSM i Azure Key Vault', + description: + "Aprèn a resoldre variables d'entorn des d'AWS SSM Parameter Store i Azure Key Vault amb la CLI, GitHub Action i SDKs de runtime d'Envilder.", backToHome: "← Tornar a l'inici", pageTitle: 'Documentació', diff --git a/src/website/src/i18n/en.ts b/src/website/src/i18n/en.ts index 531546b7..c0073dd0 100644 --- a/src/website/src/i18n/en.ts +++ b/src/website/src/i18n/en.ts @@ -3,9 +3,16 @@ import type { Translations } from './types'; export const en: Translations = { homeMeta: { title: - 'Envilder: standardize how your applications consume secrets across every environment and runtime.', + 'Open-source secret management for AWS SSM and Azure Key Vault | Envilder', description: - 'Define your environment contract once and resolve it consistently across local development, CI/CD, and runtime. Using AWS SSM Parameter Store and Azure Key Vault.', + 'Envilder is an open-source CLI, GitHub Action, and runtime SDK for loading secrets from AWS SSM Parameter Store and Azure Key Vault.', + }, + notFound: { + title: 'Page not found | Envilder', + description: 'The requested Envilder page does not exist.', + heading: 'This page was not found', + body: 'The address may be incorrect, or the page may have moved.', + home: 'Return home', }, nav: { features: 'Features', @@ -450,7 +457,9 @@ export const en: Translations = { builtWith: 'Built with Astro. Open source on GitHub.', }, changelogPage: { - title: 'Envilder Changelog | Releases & Updates', + title: 'Envilder changelog: CLI, GitHub Action, and SDK releases', + description: + 'Release history for the Envilder CLI, GitHub Action, and .NET, Python, and Node.js runtime SDKs.', backToHome: '← Back to home', fullChangelog: 'Full ', @@ -467,7 +476,9 @@ export const en: Translations = { categorySdkNodejs: 'Node.js', }, docs: { - title: 'Envilder Docs | CLI, GitHub Action & AWS SSM', + title: 'Envilder docs: AWS SSM and Azure Key Vault secrets', + description: + 'Learn how to resolve environment variables from AWS SSM Parameter Store and Azure Key Vault with the Envilder CLI, GitHub Action, and runtime SDKs.', backToHome: '← Back to home', pageTitle: 'Documentation', diff --git a/src/website/src/i18n/es.ts b/src/website/src/i18n/es.ts index 938d7a4f..59c1e9a7 100644 --- a/src/website/src/i18n/es.ts +++ b/src/website/src/i18n/es.ts @@ -3,9 +3,16 @@ import type { Translations } from './types'; export const es: Translations = { homeMeta: { title: - 'Envilder: estandariza cómo tus aplicaciones consumen secretos en cada entorno y runtime.', + 'Gestión de secretos open source para AWS SSM y Azure Key Vault | Envilder', description: - 'Define tu contrato de entorno una vez y resuélvelo de forma consistente en desarrollo local, CI/CD y runtime. Con AWS SSM Parameter Store y Azure Key Vault.', + 'Envilder es una CLI, GitHub Action y SDK de runtime open source para cargar secretos desde AWS SSM Parameter Store y Azure Key Vault.', + }, + notFound: { + title: 'Página no encontrada | Envilder', + description: 'La página de Envilder solicitada no existe.', + heading: 'No hemos encontrado esta página', + body: 'La dirección puede ser incorrecta o la página puede haberse movido.', + home: 'Volver al inicio', }, nav: { features: 'Funcionalidades', @@ -451,7 +458,9 @@ export const es: Translations = { builtWith: 'Hecho con Astro. Código abierto en GitHub.', }, changelogPage: { - title: 'Changelog Envilder | Versiones y actualizaciones', + title: 'Cambios de Envilder: versiones de la CLI, GitHub Action y SDKs', + description: + 'Historial de versiones de la CLI, GitHub Action y SDKs de runtime .NET, Python y Node.js de Envilder.', backToHome: '← Volver al inicio', fullChangelog: 'Historial de ', @@ -468,7 +477,9 @@ export const es: Translations = { categorySdkNodejs: 'Node.js', }, docs: { - title: 'Docs Envilder | CLI, GitHub Action y AWS SSM', + title: 'Docs Envilder: secretos con AWS SSM y Azure Key Vault', + description: + 'Aprende a resolver variables de entorno desde AWS SSM Parameter Store y Azure Key Vault con la CLI, GitHub Action y SDKs de runtime de Envilder.', backToHome: '← Volver al inicio', pageTitle: 'Documentación', diff --git a/src/website/src/i18n/types.ts b/src/website/src/i18n/types.ts index 6403251c..39fd820d 100644 --- a/src/website/src/i18n/types.ts +++ b/src/website/src/i18n/types.ts @@ -253,6 +253,7 @@ export interface FooterTranslations { export interface ChangelogPageTranslations { title: string; + description: string; backToHome: string; fullChangelog: string; changelogAccent: string; @@ -270,6 +271,7 @@ export interface ChangelogPageTranslations { export interface DocsTranslations { title: string; + description: string; backToHome: string; pageTitle: string; intro: string; @@ -533,6 +535,14 @@ export interface HomeMetaTranslations { description: string; } +export interface NotFoundTranslations { + title: string; + description: string; + heading: string; + body: string; + home: string; +} + export interface SponsorsTranslations { title: string; localstackAlt: string; @@ -542,6 +552,7 @@ export interface SponsorsTranslations { export interface Translations { homeMeta: HomeMetaTranslations; + notFound: NotFoundTranslations; nav: NavLinks; theme: ThemeTranslations; hero: HeroTranslations; diff --git a/src/website/src/layouts/BaseLayout.astro b/src/website/src/layouts/BaseLayout.astro index d2b270d0..40d329d3 100644 --- a/src/website/src/layouts/BaseLayout.astro +++ b/src/website/src/layouts/BaseLayout.astro @@ -3,12 +3,14 @@ export interface Props { title?: string; description?: string; lang?: string; + noindex?: boolean; } const { title = 'Envilder: centralize your secrets. One command.', description = 'A CLI tool and GitHub Action that securely centralizes environment variables from AWS SSM Parameter Store or Azure Key Vault as a single source of truth.', lang = 'en', + noindex = false, } = Astro.props; const skipLabel: Record = { @@ -18,7 +20,70 @@ const skipLabel: Record = { }; const skipText = skipLabel[lang] ?? skipLabel.en; -const canonicalUrl = new URL(Astro.url.pathname, Astro.site).href; +const siteUrl = Astro.site ?? 'https://envilder.com'; +const canonicalUrl = new URL(Astro.url.pathname, siteUrl).href; +const siteRootUrl = new URL('/', siteUrl).href; +const pathWithoutLocale = + Astro.url.pathname.replace(/^\/(?:ca|es)(?=\/|$)/, '') || '/'; +const alternateLanguages = ['en', 'ca', 'es'].map((language) => ({ + language, + href: new URL( + language === 'en' ? pathWithoutLocale : `/${language}${pathWithoutLocale}`, + siteUrl, + ).href, +})); +const isHomePage = pathWithoutLocale === '/'; +const structuredData = JSON.stringify({ + '@context': 'https://schema.org', + '@graph': [ + { + '@type': 'Organization', + '@id': `${siteRootUrl}#organization`, + name: 'Envilder', + url: siteRootUrl, + sameAs: ['https://github.com/macalbert/envilder'], + }, + { + '@type': 'WebSite', + '@id': `${siteRootUrl}#website`, + name: 'Envilder', + url: siteRootUrl, + publisher: { + '@id': `${siteRootUrl}#organization`, + }, + }, + ...(!noindex + ? [ + { + '@type': 'WebPage', + '@id': `${canonicalUrl}#webpage`, + url: canonicalUrl, + name: title, + description, + inLanguage: lang, + isPartOf: { + '@id': `${siteRootUrl}#website`, + }, + }, + ] + : []), + ...(isHomePage && !noindex + ? [ + { + '@type': 'SoftwareApplication', + '@id': `${siteRootUrl}#software`, + name: 'Envilder', + description, + applicationCategory: 'DeveloperApplication', + operatingSystem: 'Windows, macOS, Linux', + isAccessibleForFree: true, + codeRepository: 'https://github.com/macalbert/envilder', + license: 'https://github.com/macalbert/envilder/blob/main/LICENSE', + }, + ] + : []), + ], +}); --- @@ -29,6 +94,18 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).href; + {noindex && } + + {!noindex && + alternateLanguages.map((alternate) => ( + + ))} + {!noindex && } + diff --git a/src/website/src/pages/404.astro b/src/website/src/pages/404.astro new file mode 100644 index 00000000..d86399e3 --- /dev/null +++ b/src/website/src/pages/404.astro @@ -0,0 +1,60 @@ +--- +import Footer from '../components/Footer.astro'; +import Navbar from '../components/Navbar.astro'; +import { useTranslations } from '../i18n/utils'; +import BaseLayout from '../layouts/BaseLayout.astro'; + +const lang = 'en'; +const t = useTranslations(lang); +--- + + + +
+
+
+
+

404

+

{t.notFound.heading}

+

{t.notFound.body}

+ {t.notFound.home} +
+
+
+
+