fix: serve canonical, hreflang and sitemap URLs and restore Export in the PDF editor

This commit is contained in:
alam00000 committed 2026-10-23 15:01:31 +05:30
1 parent 7eec9a0d64
commit 6e8e012d3d
20 files changed
+209 -53

No files matched your search

+3 -1
View File
@@ -8,6 +8,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DIST_DIR = path.resolve(__dirname, '../dist');
const BASE_PATH = (process.env.BASE_URL || '/').replace(/\/$/, '');
const SITE_URL = (process.env.SITE_URL || 'https://www.bentopdf.com').replace(
/\/+$/,
''
@@ -125,7 +126,8 @@ function rewriteInternalLinks(document, knownSlugs) {
);
if (!match) continue;
if (!knownSlugs.has(match[1])) continue;
const target = match[1] === 'index' ? '/' : `/${match[1]}`;
const target =
match[1] === 'index' ? BASE_PATH || '/' : `${BASE_PATH}/${match[1]}`;
link.setAttribute('href', `${target}${match[2] || ''}`);
}
}
+4 -4
View File
@@ -274,7 +274,7 @@ ${renderFaqs(meta.faqs)}
'@type': 'ListItem',
position: 1,
name: 'Blog',
item: `${SITE_URL}/blog/`,
item: `${SITE_URL}/blog`,
},
{
'@type': 'ListItem',
@@ -319,9 +319,9 @@ function renderIndex(posts) {
content="Guides and honest comparisons from the maintainer of BentoPDF: how PDF tools handle your files, and how to get things done without uploading them."
/>
<meta name="robots" content="index, follow, max-image-preview:large" />
<link rel="canonical" href="${SITE_URL}/blog/" />
<link rel="canonical" href="${SITE_URL}/blog" />
<meta property="og:type" content="website" />
<meta property="og:url" content="${SITE_URL}/blog/" />
<meta property="og:url" content="${SITE_URL}/blog" />
<meta property="og:title" content="The BentoPDF Blog" />
<meta
property="og:description"
@@ -363,7 +363,7 @@ ${cards}
'@context': 'https://schema.org',
'@type': 'Blog',
name: 'The BentoPDF Blog',
url: `${SITE_URL}/blog/`,
url: `${SITE_URL}/blog`,
description:
'Guides and honest comparisons from the maintainer of BentoPDF.',
publisher: {
+6 -4
View File
@@ -60,8 +60,7 @@ function buildUrl(langPrefix, pagePath) {
if (BASE_PATH && BASE_PATH !== '') parts.push(BASE_PATH.replace(/^\//, ''));
if (langPrefix) parts.push(langPrefix);
if (pagePath) parts.push(pagePath.replace(/^\//, ''));
const url = parts.filter(Boolean).join('/').replace(/\/+$/, '') || SITE_URL;
return langPrefix && !pagePath ? `${url}/` : url;
return parts.filter(Boolean).join('/').replace(/\/+$/, '') || SITE_URL;
}
function resolveTranslation(langResources, key) {
@@ -142,7 +141,7 @@ const BREADCRUMB_MARKER = 'data-bentopdf-breadcrumb';
const BRAND_NAME = process.env.VITE_BRAND_NAME || 'BentoPDF';
function buildLocalHomeHref(lang) {
const langSegment = lang === 'en' ? '/' : `/${lang}/`;
const langSegment = lang === 'en' ? '/' : `/${lang}`;
return `${BASE_PATH}${langSegment}`.replace(/\/+/g, '/');
}
@@ -378,7 +377,10 @@ function processFileForLanguage(
newHref = `${BASE_PATH}/${lang}/${href}`;
}
link.setAttribute('href', newHref);
link.setAttribute(
'href',
newHref.replace(/^([^?#]*[^/?#])\/(?=$|[?#])/, '$1')
);
});
const result = dom.serialize();
+2 -2
View File
@@ -81,7 +81,7 @@ function buildUrl(lang, pageName) {
if (lang === 'en') {
return pagePath ? `${SITE_URL}/${pagePath}` : SITE_URL;
}
return pagePath ? `${SITE_URL}/${lang}/${pagePath}` : `${SITE_URL}/${lang}/`;
return pagePath ? `${SITE_URL}/${lang}/${pagePath}` : `${SITE_URL}/${lang}`;
}
function generateSitemap() {
@@ -177,7 +177,7 @@ function generateSitemap() {
.map((file) => file.replace('.html', ''));
for (const name of blogFiles) {
const url =
name === 'index' ? `${SITE_URL}/blog/` : `${SITE_URL}/blog/${name}`;
name === 'index' ? `${SITE_URL}/blog` : `${SITE_URL}/blog/${name}`;
const sourcePath = path.join(projectRoot, 'blog', `${name}.html`);
let lastmod;
try {
+2 -2
View File
@@ -111,7 +111,7 @@ function listDocsHtml(dir, prefix) {
);
} else if (entry.name.endsWith('.html') && entry.name !== '404.html') {
const slug = entry.name.replace(/\.html$/, '');
urls.push(slug === 'index' ? `${prefix}/` : `${prefix}/${slug}`);
urls.push(slug === 'index' ? prefix : `${prefix}/${slug}`);
}
}
return urls;
@@ -130,7 +130,7 @@ function addDocsToSitemap() {
return;
}
let xml = fs.readFileSync(sitemapPath, 'utf-8');
if (xml.includes(`<loc>${siteUrl}/docs/</loc>`)) return;
if (xml.includes(`<loc>${siteUrl}/docs</loc>`)) return;
const urls = listDocsHtml(targetDocsPath, '/docs');
const entries = urls
+24 -4
View File
@@ -85,6 +85,11 @@ for (const lang of LOCALE_DIRS) {
: {};
}
function hasTrailingSlash(href) {
const { pathname } = new URL(href);
return pathname !== '/' && pathname.endsWith('/');
}
function expectedCanonicalForFile(rel) {
const parts = rel.split('/');
const fileName = parts.pop();
@@ -121,7 +126,11 @@ function auditHtml(file, knownSlugs) {
if (!isLocalized) {
const internal = findAll(html, /<a[^>]+href=["'](\/[^"'#?]*)["']/g);
for (const href of internal) {
const clean = href.replace(/\/$/, '');
const clean = (
BASE_PATH && (href === BASE_PATH || href.startsWith(`${BASE_PATH}/`))
? href.slice(BASE_PATH.length)
: href
).replace(/\/$/, '');
if (clean === '') continue;
const seg = clean.slice(1);
if (seg.includes('/')) continue;
@@ -162,6 +171,12 @@ function auditHtml(file, knownSlugs) {
}
try {
const url = new URL(actual);
if (hasTrailingSlash(actual)) {
fail(
'canonical',
`${file.rel}: canonical "${actual}" ends with "/" (the server redirects it to the slashless form)`
);
}
if (url.hostname !== HOST) {
warn(
'canonical',
@@ -230,11 +245,10 @@ function auditHtml(file, knownSlugs) {
`${file.rel}: hreflang href host "${url.hostname}" != expected "${HOST}"`
);
}
const trimmed = url.pathname.replace(/^\/|\/$/g, '');
if (LOCALE_DIRS.has(trimmed) && !url.pathname.endsWith('/')) {
if (hasTrailingSlash(m[1])) {
fail(
'hreflang',
`${file.rel}: language-home hreflang "${m[1]}" must end with "/" (the server 301s the slashless form)`
`${file.rel}: hreflang "${m[1]}" ends with "/" (the server redirects it to the slashless form)`
);
}
} catch {
@@ -334,6 +348,12 @@ function auditSitemap() {
try {
const url = new URL(loc);
if (hasTrailingSlash(loc)) {
fail(
'sitemap',
`sitemap URL ends with "/" (the server redirects it to the slashless form): ${loc}`
);
}
if (url.hostname !== HOST) {
fail(
'sitemap',