Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .github/workflows/seo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Quality & SEO Audit
on: [pull_request]

jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

# Cache dependencies for faster builds
- uses: actions/cache@v4
id: bun-cache
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('website/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-

- uses: actions/cache@v4
id: node-modules-cache
with:
path: website/node_modules
key: ${{ runner.os }}-node-modules-${{ hashFiles('website/package.json') }}
restore-keys: |
${{ runner.os }}-node-modules-

# Install Chromium for Puppeteer (required by Unlighthouse)
# This action sets CHROMIUM_PATH environment variable automatically
- name: Setup Chromium
uses: browser-actions/setup-chromium@v1
with:
chromium-version: latest

# 1. Build and Start Server
# NOTE: Astro outputs to 'dist' directory by default
# Set SITE_BASE_URL to localhost for CI so all links point to localhost instead of production
- name: Install Dependencies & Build
working-directory: website
env:
SITE_BASE_URL: "http://localhost:3000"
run: |
bun install --frozen-lockfile
bun run build

# 2. Start Static Server (Astro outputs to 'dist' directory)
- name: Start Static Server
working-directory: website
run: |
npx --yes serve@latest dist -p 3000 &
npx wait-on http://localhost:3000 --timeout 60000 || (echo "Server failed to start" && exit 1)

# 3. FAIL FAST: Broken Link Checker
# Only check links on localhost:3000, skip external links (e.g., https://www.quantus.com)
- name: Check for Broken Links
run: |
npx --yes linkinator http://localhost:3000 --recurse --skip "^(?!http://localhost:3000)"

# 4. DEEP AUDIT: Unlighthouse
# Scans all pages and fails if SEO score is below 100 (configured in unlighthouse.config.ts)
# The '--build-static' flag generates the HTML report files.
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD tells Puppeteer to use the system Chromium from setup-chromium
# CHROMIUM_PATH is automatically set by the setup-chromium action
- name: Run Unlighthouse Site-Wide Audit
working-directory: website
env:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true"
PUPPETEER_EXECUTABLE_PATH: ${{ env.CHROMIUM_PATH }}
run: |
npx --yes @unlighthouse/cli@latest --build-static

# 5. UPLOAD ARTIFACT
# This takes the generated report and saves it as a zip file.
# Note: Unlighthouse runs in website/ directory, so output is at website/.unlighthouse
- name: Debug - List Unlighthouse output
if: always()
run: |
echo "Checking for Unlighthouse output..."
ls -la website/.unlighthouse 2>/dev/null || echo "website/.unlighthouse not found"
ls -la website/.unlighthouse/client 2>/dev/null || echo "website/.unlighthouse/client not found"
find website -name ".unlighthouse" -type d 2>/dev/null || echo "No .unlighthouse directories found"

- name: Create SEO Report Zip
if: always()
run: |
cd website
zip -r seo-report.zip .unlighthouse/ || echo "Failed to create zip, but continuing..."
ls -lh seo-report.zip || echo "Zip file not created"

- name: Upload SEO Report
uses: actions/upload-artifact@v4
if: always() # Upload even if previous step failed
with:
name: seo-report
path: website/seo-report.zip
if-no-files-found: warn # Warn instead of error if no files found
retention-days: 7 # Auto-delete after 7 days to save space
27 changes: 19 additions & 8 deletions website/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import sitemap from "@astrojs/sitemap";
import react from "@astrojs/react";
import rehypeExternalLinks from "./src/utils/rehype-external-links.ts";

const SITE_BASE_URL = "https://www.quantus.com";
const SITE_BASE_URL = process.env.SITE_BASE_URL || "https://www.quantus.com";
const DEFAULT_LOCALE = "en-US";
const SUPPORTED_LOCALES = [
"en-US",
Expand All @@ -30,18 +30,13 @@ const LOCALES_MAP = {
"hi-IN": "hi-IN",
};

const HOMEPAGE_LINK = SUPPORTED_LOCALES.map((locale) => {
if (locale === "en-US") return `${SITE_BASE_URL}/`;
return `${SITE_BASE_URL}/${locale}`;
});

// https://astro.build/config
export default defineConfig({
vite: {
// @ts-ignore
plugins: [tailwindcss()],
},
site: "https://www.quantus.com",
site: SITE_BASE_URL,
markdown: {
rehypePlugins: [rehypeExternalLinks],
},
Expand All @@ -52,7 +47,23 @@ export default defineConfig({
lastmod: new Date(),
i18n: { defaultLocale: DEFAULT_LOCALE, locales: LOCALES_MAP },
serialize: (item) => {
if (HOMEPAGE_LINK.includes(item.url)) {
// Read environment variable at runtime (during build)
// The serialize function runs during sitemap generation, so process.env should be available
const envBaseUrl = typeof process !== 'undefined' && process.env?.SITE_BASE_URL;
const baseUrl = envBaseUrl || SITE_BASE_URL;

// Replace production URLs with the correct base URL if env var is set
if (envBaseUrl && item.url) {
item.url = item.url.replace(/^https?:\/\/[^/]+/, baseUrl);
}

// Check if this is a homepage link using the current base URL
const homepageLinks = SUPPORTED_LOCALES.map((locale) => {
if (locale === DEFAULT_LOCALE) return `${baseUrl}/`;
return `${baseUrl}/${locale}`;
});

if (homepageLinks.includes(item.url)) {
item.priority = 1;
}
return item;
Expand Down
Loading
Loading