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
1 change: 0 additions & 1 deletion docs-site/build_dir

This file was deleted.

2 changes: 1 addition & 1 deletion docs-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"preclean-build": "npm run prepare-content",
"start": "docusaurus start",
"build": "docusaurus build",
"clean-build": "docusaurus clear && docusaurus build && cp ./CNAME ./build/CNAME",
"clean-build": "docusaurus clear && docusaurus build",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations --locale en && node ../scripts/capture-docs-ui.js"
},
Expand Down
4 changes: 1 addition & 3 deletions docs-site/publish-from-actions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@

set -e

if [ -z "${BUILD_DIR:-}" ]; then
source ./build_dir
fi
: "${BUILD_DIR:?BUILD_DIR is required}"

echo "#################################################"
echo "Changing directory to 'BUILD_DIR' $BUILD_DIR ..."
Expand Down
File renamed without changes.
13 changes: 3 additions & 10 deletions marketing-site/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import type { MetadataRoute } from "next";
import { DEFAULT_LOCALE, ENABLED_LOCALES, localizedPath } from "@/i18n/config";
import routes from "@/routes.json";

export const dynamic = "force-static";

export default function sitemap(): MetadataRoute.Sitemap {
const base = "https://caprover.com";
const lastModified = new Date("2026-08-20");
const paths = [
{ path: "/", priority: 1 },
{ path: "/compare/", priority: 0.7 },
{ path: "/compare/coolify/", priority: 0.7 },
{ path: "/compare/dokploy/", priority: 0.7 },
{ path: "/compare/dokku/", priority: 0.6 },
];

return paths.flatMap(({ path, priority }) => {
return Object.values(routes).flatMap(({ path, sitemapPriority }) => {
const defaultUrl = `${base}${localizedPath(path, DEFAULT_LOCALE)}`;
const languages = Object.fromEntries(
ENABLED_LOCALES.map((locale) => [
Expand All @@ -28,7 +21,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
url: `${base}${localizedPath(path, locale.code)}`,
lastModified,
changeFrequency: "monthly" as const,
priority,
priority: sitemapPriority,
alternates,
}));
});
Expand Down
12 changes: 9 additions & 3 deletions marketing-site/features/comparison/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
} from "@/i18n/config";
import { getMessages, messageList } from "@/i18n/messages";
import { LocaleSwitcher } from "@/app/locale-switcher";
import routes from "@/routes.json";
import {
getComparisonData,
type ComparisonRow,
Expand All @@ -28,10 +29,10 @@
return (
<a
className="compare-logo"
href={internal("/", locale)}
href={internal(routes.home.path, locale)}
aria-label={copy.brandAriaLabel}
>
<img

Check warning on line 35 in marketing-site/features/comparison/components.tsx

View workflow job for this annotation

GitHub Actions / build-combined-site

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
src={`${BASE_PATH}${ASSET_PATH}/caprover-logo.png`}
alt=""
aria-hidden="true"
Expand All @@ -57,7 +58,9 @@
role="navigation"
aria-label={copy.navigationAriaLabel}
>
<a href={internal("/compare/", locale)}>{copy.navigation.hub}</a>
<a href={internal(routes.comparisonHub.path, locale)}>
{copy.navigation.hub}
</a>
<a href={docsUrl("get-started", locale)}>{copy.navigation.docs}</a>
<a href={GITHUB}>{copy.navigation.github}</a>
<LocaleSwitcher
Expand Down Expand Up @@ -321,7 +324,10 @@
.map((link) => (
<a
className="matchup-card"
href={internal(`/compare/${link.key}/`, locale)}
href={internal(
routes[link.key as Exclude<Product, "caprover">].path,
locale,
)}
key={link.key}
>
<span>{copy.kicker}</span>
Expand Down
3 changes: 2 additions & 1 deletion marketing-site/features/comparison/coolify/page-content.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEFAULT_LOCALE, docsUrl, type Locale } from "@/i18n/config";
import { getMessages, messageList } from "@/i18n/messages";
import routes from "@/routes.json";
import {
ChoiceGrid,
ComparePage,
Expand All @@ -23,7 +24,7 @@ export function CoolifyComparisonPage({
const copy = getMessages(locale).comparisonPages.coolify;
const sources = copy.sourceLabels;
return (
<ComparePage locale={locale} path="/compare/coolify/">
<ComparePage locale={locale} path={routes.coolify.path}>
<PageHero {...copy.hero}>
<div className="verdict">
<strong>{copy.hero.verdictTitle}</strong>
Expand Down
3 changes: 2 additions & 1 deletion marketing-site/features/comparison/dokku/page-content.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEFAULT_LOCALE, docsUrl, type Locale } from "@/i18n/config";
import { getMessages, messageList } from "@/i18n/messages";
import routes from "@/routes.json";
import {
ChoiceGrid,
ComparePage,
Expand All @@ -23,7 +24,7 @@ export function DokkuComparisonPage({
const copy = getMessages(locale).comparisonPages.dokku;
const sources = copy.sourceLabels;
return (
<ComparePage locale={locale} path="/compare/dokku/">
<ComparePage locale={locale} path={routes.dokku.path}>
<PageHero {...copy.hero}>
<div className="verdict">
<strong>{copy.hero.verdictTitle}</strong>
Expand Down
3 changes: 2 additions & 1 deletion marketing-site/features/comparison/dokploy/page-content.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEFAULT_LOCALE, docsUrl, type Locale } from "@/i18n/config";
import { getMessages, messageList } from "@/i18n/messages";
import routes from "@/routes.json";
import {
ChoiceGrid,
ComparePage,
Expand All @@ -23,7 +24,7 @@ export function DokployComparisonPage({
const copy = getMessages(locale).comparisonPages.dokploy;
const sources = copy.sourceLabels;
return (
<ComparePage locale={locale} path="/compare/dokploy/">
<ComparePage locale={locale} path={routes.dokploy.path}>
<PageHero {...copy.hero}>
<div className="verdict">
<strong>{copy.hero.verdictTitle}</strong>
Expand Down
3 changes: 2 additions & 1 deletion marketing-site/features/comparison/page-content.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEFAULT_LOCALE, docsUrl, type Locale } from "@/i18n/config";
import { getMessages } from "@/i18n/messages";
import routes from "@/routes.json";
import {
ComparePage,
ComparisonPrinciples,
Expand All @@ -21,7 +22,7 @@ export function ComparisonHubPage({
const copy = getMessages(locale).comparisonPages.hub;
const sources = copy.sourceLabels;
return (
<ComparePage locale={locale} path="/compare/">
<ComparePage locale={locale} path={routes.comparisonHub.path}>
<PageHero {...copy.hero}>
<ProofStrip locale={locale} />
</PageHero>
Expand Down
11 changes: 6 additions & 5 deletions marketing-site/i18n/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
import routes from "../routes.json";
import { getFlatMessages, type MessageKey } from "./messages";
import {
DEFAULT_LOCALE,
Expand All @@ -11,27 +12,27 @@ const SITE = "https://caprover.com";

const PAGE_METADATA = {
home: {
path: "/",
path: routes.home.path,
titleKey: "homepage.metadata.title",
descriptionKey: "homepage.metadata.description",
},
comparisonHub: {
path: "/compare/",
path: routes.comparisonHub.path,
titleKey: "comparisonPages.hub.metadata.title",
descriptionKey: "comparisonPages.hub.metadata.description",
},
coolify: {
path: "/compare/coolify/",
path: routes.coolify.path,
titleKey: "comparisonPages.coolify.metadata.title",
descriptionKey: "comparisonPages.coolify.metadata.description",
},
dokploy: {
path: "/compare/dokploy/",
path: routes.dokploy.path,
titleKey: "comparisonPages.dokploy.metadata.title",
descriptionKey: "comparisonPages.dokploy.metadata.description",
},
dokku: {
path: "/compare/dokku/",
path: routes.dokku.path,
titleKey: "comparisonPages.dokku.metadata.title",
descriptionKey: "comparisonPages.dokku.metadata.description",
},
Expand Down
22 changes: 22 additions & 0 deletions marketing-site/routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"home": {
"path": "/",
"sitemapPriority": 1
},
"comparisonHub": {
"path": "/compare/",
"sitemapPriority": 0.7
},
"coolify": {
"path": "/compare/coolify/",
"sitemapPriority": 0.7
},
"dokploy": {
"path": "/compare/dokploy/",
"sitemapPriority": 0.7
},
"dokku": {
"path": "/compare/dokku/",
"sitemapPriority": 0.6
}
}
44 changes: 32 additions & 12 deletions marketing-site/tests/static-export.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ const expectedAssets = [
"caprover-workflow.png",
"slack-icon.png",
];
const routes = [
{ path: "/", titleKey: "homepage.hero.titleLine1" },
{ path: "/compare/", titleKey: "comparisonPages.hub.hero.title" },
{ path: "/compare/coolify/", titleKey: "comparisonPages.coolify.hero.title" },
{ path: "/compare/dokploy/", titleKey: "comparisonPages.dokploy.hero.title" },
{ path: "/compare/dokku/", titleKey: "comparisonPages.dokku.hero.title" },
];
const routeManifest = JSON.parse(await readFile("routes.json", "utf8"));
const routeTitleKeys = {
home: "homepage.hero.titleLine1",
comparisonHub: "comparisonPages.hub.hero.title",
coolify: "comparisonPages.coolify.hero.title",
dokploy: "comparisonPages.dokploy.hero.title",
dokku: "comparisonPages.dokku.hero.title",
};
const routes = Object.entries(routeManifest).map(([name, route]) => ({
path: route.path,
titleKey: routeTitleKeys[name],
}));
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
const assetPath = "homepage-assets";
const locales = JSON.parse(await readFile("../content/locales.json", "utf8"));
Expand Down Expand Up @@ -93,11 +98,26 @@ test("exports complete localized website routes", async () => {
});

test("keeps comparison regressions covered across locales", async () => {
const homepage = await readFile("out/index.html", "utf8");
const hub = await readFile("out/compare/index.html", "utf8");
const coolify = await readFile("out/compare/coolify/index.html", "utf8");
const dokploy = await readFile("out/compare/dokploy/index.html", "utf8");
const dokku = await readFile("out/compare/dokku/index.html", "utf8");
const homepage = await readFile(
`out${routeManifest.home.path}index.html`,
"utf8",
);
const hub = await readFile(
`out${routeManifest.comparisonHub.path}index.html`,
"utf8",
);
const coolify = await readFile(
`out${routeManifest.coolify.path}index.html`,
"utf8",
);
const dokploy = await readFile(
`out${routeManifest.dokploy.path}index.html`,
"utf8",
);
const dokku = await readFile(
`out${routeManifest.dokku.path}index.html`,
"utf8",
);
const sitemap = await readFile("out/sitemap.xml", "utf8");

assert.doesNotMatch(homepage, /href=["'][^"']*\/compare\/?["']/);
Expand Down
32 changes: 2 additions & 30 deletions scripts/capture-docs-ui.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const fs = require("fs");
const path = require("path");
const { fromDocusaurusCatalogs } = require("./docs-ui-catalogs");

const root = path.resolve(__dirname, "..");
const locales = require(path.join(root, "content", "locales.json"));
Expand All @@ -25,39 +26,10 @@ function readCatalog(...parts) {
return JSON.parse(fs.readFileSync(file, "utf8"));
}

function message(catalog, key) {
if (!catalog[key] || typeof catalog[key].message !== "string") {
throw new Error(`Generated documentation UI key is missing: ${key}`);
}
return catalog[key].message;
}

const docs = readCatalog("docusaurus-plugin-content-docs", "current.json");
const navbar = readCatalog("docusaurus-theme-classic", "navbar.json");
const footer = readCatalog("docusaurus-theme-classic", "footer.json");
const result = {
"version.label": message(docs, "version.label"),
"navbar.Docs": message(navbar, "item.label.Docs"),
"navbar.GitHub": message(navbar, "item.label.GitHub"),
"navbar.Slack Group": message(navbar, "item.label.Slack Group"),
"footer.column.Docs": message(footer, "link.title.Docs"),
"footer.column.Community": message(footer, "link.title.Community"),
"footer.column.More": message(footer, "link.title.More"),
"footer.Getting Started": message(
footer,
"link.item.label.Getting Started",
),
"footer.X": message(footer, "link.item.label.X"),
"footer.Slack Group": message(footer, "link.item.label.Slack Group"),
"footer.GitHub": message(footer, "link.item.label.GitHub"),
};

for (const [key, value] of Object.entries(docs)) {
const prefix = "sidebar.docs.category.";
if (key.startsWith(prefix)) {
result[`sidebar.${key.slice(prefix.length)}`] = value.message;
}
}
const result = fromDocusaurusCatalogs({ docs, navbar, footer });

fs.writeFileSync(canonical, `${JSON.stringify(result, null, 2)}\n`);
console.log(`Updated ${path.relative(root, canonical)}`);
14 changes: 6 additions & 8 deletions scripts/compose-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@ const root = process.cwd();
const docsSite = path.join(root, "docs-site/build");
const marketingSite = path.join(root, "marketing-site/out");
const combinedSite = path.join(root, "build/combined-site");
const cnameSource = path.join(root, "docs-site/static/CNAME");
const locales = JSON.parse(
await readFile(path.join(root, "content/locales.json"), "utf8"),
).filter((locale) => locale.enabled);
const defaultLocale = locales.find((locale) => locale.default);
const marketingRoutes = [
"",
"compare",
"compare/coolify",
"compare/dokploy",
"compare/dokku",
];
const marketingRoutes = Object.values(
JSON.parse(await readFile(path.join(root, "marketing-site/routes.json"), "utf8")),
).map((route) => route.path.replace(/^\//, "").replace(/\/$/, ""));

assert(defaultLocale, "An enabled default locale is required");

Expand Down Expand Up @@ -127,6 +124,7 @@ await Promise.all([
),
requirePath(path.join(docsSite, "img/logo.png")),
requirePath(path.join(docsSite, "CNAME")),
requirePath(cnameSource),
...locales.flatMap((locale) =>
marketingRoutes.map((route) => requirePath(marketingRouteFile(locale, route))),
),
Expand All @@ -139,7 +137,7 @@ await Promise.all([
requireNoCollision("compare"),
]);

const originalCname = await readFile(path.join(docsSite, "CNAME"));
const originalCname = await readFile(cnameSource);
assert.equal(originalCname.toString().trim(), "caprover.com");

const [docsBefore, imagesBefore] = await Promise.all([
Expand Down
Loading
Loading