Add crawlable landing content, keyword tool titles and og:image subpath fix
This commit is contained in:
@@ -7,12 +7,12 @@
|
||||
"byTool": {
|
||||
"certSign": {
|
||||
"image": "/og_images/certSign.png",
|
||||
"title": "Certificate Sign - Stirling PDF",
|
||||
"title": "Sign PDF with a Certificate - Stirling PDF",
|
||||
"description": "Sign PDF documents using digital certificates"
|
||||
},
|
||||
"sign": {
|
||||
"image": "/og_images/sign.png",
|
||||
"title": "Sign - Stirling PDF",
|
||||
"title": "Sign PDF - Stirling PDF",
|
||||
"description": "Adds signature to PDF by drawing, text or image"
|
||||
},
|
||||
"sharedSign": {
|
||||
@@ -22,122 +22,122 @@
|
||||
},
|
||||
"addText": {
|
||||
"image": "/og_images/add-text.png",
|
||||
"title": "Add Text - Stirling PDF",
|
||||
"title": "Add Text to PDF - Stirling PDF",
|
||||
"description": "Add custom text anywhere in your PDF"
|
||||
},
|
||||
"addPassword": {
|
||||
"image": "/og_images/add-password.png",
|
||||
"title": "Add Password - Stirling PDF",
|
||||
"title": "Password Protect PDF - Stirling PDF",
|
||||
"description": "Add password protection and restrictions to PDF files"
|
||||
},
|
||||
"removePassword": {
|
||||
"image": "/og_images/remove-password.png",
|
||||
"title": "Remove Password - Stirling PDF",
|
||||
"title": "Remove PDF Password - Stirling PDF",
|
||||
"description": "Remove password protection from PDF documents"
|
||||
},
|
||||
"removePages": {
|
||||
"image": "/og_images/remove.png",
|
||||
"title": "Remove Pages - Stirling PDF",
|
||||
"title": "Delete Pages from PDF - Stirling PDF",
|
||||
"description": "Remove specific pages from a PDF document"
|
||||
},
|
||||
"removeBlanks": {
|
||||
"image": "/og_images/remove-blank-pages.png",
|
||||
"title": "Remove Blank Pages - Stirling PDF",
|
||||
"title": "Remove Blank Pages from PDF - Stirling PDF",
|
||||
"description": "Remove blank pages from PDF documents"
|
||||
},
|
||||
"removeAnnotations": {
|
||||
"image": "/og_images/remove-annotations.png",
|
||||
"title": "Remove Annotations - Stirling PDF",
|
||||
"title": "Remove Annotations from PDF - Stirling PDF",
|
||||
"description": "Remove annotations and comments from PDF documents"
|
||||
},
|
||||
"removeImage": {
|
||||
"image": "/og_images/remove-image.png",
|
||||
"title": "Remove Images - Stirling PDF",
|
||||
"title": "Remove Images from PDF - Stirling PDF",
|
||||
"description": "Remove all images from a PDF document"
|
||||
},
|
||||
"changePermissions": {
|
||||
"image": "/og_images/change-permissions.png",
|
||||
"title": "Change Permissions - Stirling PDF",
|
||||
"title": "Change PDF Permissions - Stirling PDF",
|
||||
"description": "Change document restrictions and permissions"
|
||||
},
|
||||
"watermark": {
|
||||
"image": "/og_images/add-watermark.png",
|
||||
"title": "Add Watermark - Stirling PDF",
|
||||
"title": "Add Watermark to PDF - Stirling PDF",
|
||||
"description": "Add a custom watermark to your PDF document."
|
||||
},
|
||||
"sanitize": {
|
||||
"image": "/og_images/sanitize.png",
|
||||
"title": "Sanitize - Stirling PDF",
|
||||
"title": "Sanitize PDF - Stirling PDF",
|
||||
"description": "Remove potentially harmful elements from PDF files"
|
||||
},
|
||||
"split": {
|
||||
"image": "/og_images/split.png",
|
||||
"title": "Split - Stirling PDF",
|
||||
"title": "Split PDF - Stirling PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"merge": {
|
||||
"image": "/og_images/mergePdfs.png",
|
||||
"title": "Merge - Stirling PDF",
|
||||
"title": "Merge PDF - Stirling PDF",
|
||||
"description": "Merge multiple PDFs into a single document"
|
||||
},
|
||||
"convert": {
|
||||
"image": "/og_images/convert.png",
|
||||
"title": "Convert - Stirling PDF",
|
||||
"title": "PDF Converter - Stirling PDF",
|
||||
"description": "Convert files to and from PDF format"
|
||||
},
|
||||
"ocr": {
|
||||
"image": "/og_images/ocr.png",
|
||||
"title": "OCR - Stirling PDF",
|
||||
"title": "OCR PDF - Stirling PDF",
|
||||
"description": "Extract text from scanned PDFs using Optical Character Recognition"
|
||||
},
|
||||
"addImage": {
|
||||
"image": "/og_images/add-image.png",
|
||||
"title": "Add Image - Stirling PDF",
|
||||
"title": "Add Image to PDF - Stirling PDF",
|
||||
"description": "Add images anywhere in your PDF"
|
||||
},
|
||||
"rotate": {
|
||||
"image": "/og_images/rotate.png",
|
||||
"title": "Rotate - Stirling PDF",
|
||||
"title": "Rotate PDF - Stirling PDF",
|
||||
"description": "Easily rotate your PDFs."
|
||||
},
|
||||
"annotate": {
|
||||
"image": "/og_images/annotate.png",
|
||||
"title": "Annotate - Stirling PDF",
|
||||
"title": "Annotate PDF - Stirling PDF",
|
||||
"description": "Highlight, draw, add notes, and shapes directly in the viewer"
|
||||
},
|
||||
"scannerImageSplit": {
|
||||
"image": "/og_images/detect-split-scanned-photos.png",
|
||||
"title": "Detect & Split Scanned Photos - Stirling PDF",
|
||||
"title": "Split Scanned Photos - Stirling PDF",
|
||||
"description": "Detect and split scanned photos into separate pages"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"image": "/og_images/edit-table-of-contents.png",
|
||||
"title": "Edit Table of Contents - Stirling PDF",
|
||||
"title": "Edit PDF Bookmarks - Stirling PDF",
|
||||
"description": "Add or edit bookmarks and table of contents in PDF documents"
|
||||
},
|
||||
"scannerEffect": {
|
||||
"image": "/og_images/scanner-effect.png",
|
||||
"title": "Scanner Effect - Stirling PDF",
|
||||
"title": "Make a PDF Look Scanned - Stirling PDF",
|
||||
"description": "Create a PDF that looks like it was scanned"
|
||||
},
|
||||
"autoRename": {
|
||||
"image": "/og_images/auto-rename-pdf-file.png",
|
||||
"title": "Auto Rename PDF File - Stirling PDF",
|
||||
"title": "Auto Rename PDF Files - Stirling PDF",
|
||||
"description": "Automatically rename PDF files based on their content"
|
||||
},
|
||||
"pageLayout": {
|
||||
"image": "/og_images/multi-page-layout.png",
|
||||
"title": "Multi-Page Layout - Stirling PDF",
|
||||
"title": "PDF Multi-Page Layout - Stirling PDF",
|
||||
"description": "Merge multiple pages of a PDF document into a single page"
|
||||
},
|
||||
"scalePages": {
|
||||
"image": "/og_images/adjust-page-size-scale.png",
|
||||
"title": "Adjust page size/scale - Stirling PDF",
|
||||
"title": "Resize PDF Pages - Stirling PDF",
|
||||
"description": "Change the size/scale of a page and/or its contents."
|
||||
},
|
||||
"adjustContrast": {
|
||||
"image": "/og_images/adjust-colors-contrast.png",
|
||||
"title": "Adjust Colors/Contrast - Stirling PDF",
|
||||
"title": "Adjust PDF Colors and Contrast - Stirling PDF",
|
||||
"description": "Adjust colors and contrast of PDF documents"
|
||||
},
|
||||
"crop": {
|
||||
@@ -147,37 +147,37 @@
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"image": "/og_images/single-large-page.png",
|
||||
"title": "PDF to Single Large Page - Stirling PDF",
|
||||
"title": "Combine PDF into a Single Page - Stirling PDF",
|
||||
"description": "Merges all PDF pages into one large single page"
|
||||
},
|
||||
"repair": {
|
||||
"image": "/og_images/repair.png",
|
||||
"title": "Repair - Stirling PDF",
|
||||
"title": "Repair PDF - Stirling PDF",
|
||||
"description": "Repair corrupted or damaged PDF files"
|
||||
},
|
||||
"compare": {
|
||||
"image": "/og_images/compare.png",
|
||||
"title": "Compare - Stirling PDF",
|
||||
"title": "Compare PDF Files - Stirling PDF",
|
||||
"description": "Compare two PDF documents and highlight differences"
|
||||
},
|
||||
"addPageNumbers": {
|
||||
"image": "/og_images/add-page-numbers.png",
|
||||
"title": "Add Page Numbers - Stirling PDF",
|
||||
"title": "Add Page Numbers to PDF - Stirling PDF",
|
||||
"description": "Add Page numbers throughout a document in a set location"
|
||||
},
|
||||
"redact": {
|
||||
"image": "/og_images/redact.png",
|
||||
"title": "Redact - Stirling PDF",
|
||||
"title": "Redact PDF - Stirling PDF",
|
||||
"description": "Permanently remove sensitive information from PDF documents"
|
||||
},
|
||||
"flatten": {
|
||||
"image": "/og_images/flatten.png",
|
||||
"title": "Flatten - Stirling PDF",
|
||||
"title": "Flatten PDF - Stirling PDF",
|
||||
"description": "Remove all interactive elements and forms from a PDF"
|
||||
},
|
||||
"removeCertSign": {
|
||||
"image": "/og_images/remove-certificate-sign.png",
|
||||
"title": "Remove Certificate Sign - Stirling PDF",
|
||||
"title": "Remove PDF Certificate Signature - Stirling PDF",
|
||||
"description": "Remove digital signature from PDF documents"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
@@ -187,22 +187,22 @@
|
||||
},
|
||||
"compress": {
|
||||
"image": "/og_images/compress.png",
|
||||
"title": "Compress - Stirling PDF",
|
||||
"title": "Compress PDF - Stirling PDF",
|
||||
"description": "Compress PDFs to reduce their file size."
|
||||
},
|
||||
"extractPages": {
|
||||
"image": "/og_images/extract-pages.png",
|
||||
"title": "Extract Pages - Stirling PDF",
|
||||
"title": "Extract PDF Pages - Stirling PDF",
|
||||
"description": "Extract specific pages from a PDF document"
|
||||
},
|
||||
"reorganizePages": {
|
||||
"image": "/og_images/reorganize-pages.png",
|
||||
"title": "Reorganize Pages - Stirling PDF",
|
||||
"title": "Organize PDF Pages - Stirling PDF",
|
||||
"description": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||
},
|
||||
"extractImages": {
|
||||
"image": "/og_images/extract-images.png",
|
||||
"title": "Extract Images - Stirling PDF",
|
||||
"title": "Extract Images from PDF - Stirling PDF",
|
||||
"description": "Extract images from PDF documents"
|
||||
},
|
||||
"addStamp": {
|
||||
@@ -212,22 +212,22 @@
|
||||
},
|
||||
"addAttachments": {
|
||||
"image": "/og_images/add-attachments.png",
|
||||
"title": "Add Attachments - Stirling PDF",
|
||||
"title": "Add Attachments to PDF - Stirling PDF",
|
||||
"description": "Add or remove embedded files (attachments) to/from a PDF"
|
||||
},
|
||||
"changeMetadata": {
|
||||
"image": "/og_images/change-metadata.png",
|
||||
"title": "Change Metadata - Stirling PDF",
|
||||
"title": "Edit PDF Metadata - Stirling PDF",
|
||||
"description": "Change/Remove/Add metadata from a PDF document"
|
||||
},
|
||||
"overlayPdfs": {
|
||||
"image": "/og_images/overlay-pdfs.png",
|
||||
"title": "Overlay Pdfs - Stirling PDF",
|
||||
"title": "Overlay PDFs - Stirling PDF",
|
||||
"description": "The Free Adobe Acrobat alternative (10M+ Downloads)"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"image": "/og_images/get-all-info-on-pdf.png",
|
||||
"title": "Get ALL Info on PDF - Stirling PDF",
|
||||
"title": "PDF Info and Metadata Viewer - Stirling PDF",
|
||||
"description": "Grabs any and all information possible on PDFs"
|
||||
},
|
||||
"validateSignature": {
|
||||
@@ -242,42 +242,42 @@
|
||||
},
|
||||
"replaceColor": {
|
||||
"image": "/og_images/replace-and-invert-color.png",
|
||||
"title": "Replace & Invert Color - Stirling PDF",
|
||||
"title": "Replace and Invert PDF Colors - Stirling PDF",
|
||||
"description": "Replace or invert colors in PDF documents"
|
||||
},
|
||||
"showJS": {
|
||||
"image": "/og_images/show-javascript.png",
|
||||
"title": "Show JavaScript - Stirling PDF",
|
||||
"title": "Show JavaScript in PDF - Stirling PDF",
|
||||
"description": "Extract and display JavaScript code from PDF documents"
|
||||
},
|
||||
"bookletImposition": {
|
||||
"image": "/og_images/booklet-imposition.png",
|
||||
"title": "Booklet Imposition - Stirling PDF",
|
||||
"title": "Create a PDF Booklet - Stirling PDF",
|
||||
"description": "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
||||
},
|
||||
"pdfTextEditor": {
|
||||
"image": "/og_images/pdf-text-editor.png",
|
||||
"title": "PDF Text Editor - Stirling PDF",
|
||||
"title": "Edit PDF Text - Stirling PDF",
|
||||
"description": "Review and edit text and images in PDFs with grouped text editing and PDF regeneration"
|
||||
},
|
||||
"formFill": {
|
||||
"image": "/og_images/form-fill.png",
|
||||
"title": "Fill Form - Stirling PDF",
|
||||
"title": "Fill PDF Forms - Stirling PDF",
|
||||
"description": "Fill PDF form fields interactively with a visual editor"
|
||||
},
|
||||
"multiTool": {
|
||||
"image": "/og_images/multi-tool.png",
|
||||
"title": "Multi-Tool - Stirling PDF",
|
||||
"title": "PDF Page Editor - Stirling PDF",
|
||||
"description": "Use multiple tools on a single PDF document"
|
||||
},
|
||||
"read": {
|
||||
"image": "/og_images/read.png",
|
||||
"title": "Read - Stirling PDF",
|
||||
"title": "PDF Viewer - Stirling PDF",
|
||||
"description": "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
||||
},
|
||||
"automate": {
|
||||
"image": "/og_images/automate.png",
|
||||
"title": "Automate - Stirling PDF",
|
||||
"title": "PDF Workflow Automation - Stirling PDF",
|
||||
"description": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||
},
|
||||
"devApi": {
|
||||
@@ -775,5 +775,303 @@
|
||||
"/settings/help": "/settings/help",
|
||||
"/settings/legal": "/settings/legal",
|
||||
"/settings/payg": "/settings/payg"
|
||||
}
|
||||
},
|
||||
"navLinks": [
|
||||
{
|
||||
"path": "/add-attachments",
|
||||
"label": "Add Attachments to PDF"
|
||||
},
|
||||
{
|
||||
"path": "/add-image",
|
||||
"label": "Add Image to PDF"
|
||||
},
|
||||
{
|
||||
"path": "/add-page-numbers",
|
||||
"label": "Add Page Numbers to PDF"
|
||||
},
|
||||
{
|
||||
"path": "/add-stamp",
|
||||
"label": "Add Stamp to PDF"
|
||||
},
|
||||
{
|
||||
"path": "/add-text",
|
||||
"label": "Add Text to PDF"
|
||||
},
|
||||
{
|
||||
"path": "/watermark",
|
||||
"label": "Add Watermark to PDF"
|
||||
},
|
||||
{
|
||||
"path": "/adjust-contrast",
|
||||
"label": "Adjust PDF Colors and Contrast"
|
||||
},
|
||||
{
|
||||
"path": "/annotations",
|
||||
"label": "Annotate PDF"
|
||||
},
|
||||
{
|
||||
"path": "/auto-rename",
|
||||
"label": "Auto Rename PDF Files"
|
||||
},
|
||||
{
|
||||
"path": "/cbr-to-pdf",
|
||||
"label": "CBR to PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/cbz-to-pdf",
|
||||
"label": "CBZ to PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/change-permissions",
|
||||
"label": "Change PDF Permissions"
|
||||
},
|
||||
{
|
||||
"path": "/single-large-page",
|
||||
"label": "Combine PDF into a Single Page"
|
||||
},
|
||||
{
|
||||
"path": "/compare",
|
||||
"label": "Compare PDF Files"
|
||||
},
|
||||
{
|
||||
"path": "/compress",
|
||||
"label": "Compress PDF"
|
||||
},
|
||||
{
|
||||
"path": "/booklet-imposition",
|
||||
"label": "Create a PDF Booklet"
|
||||
},
|
||||
{
|
||||
"path": "/crop",
|
||||
"label": "Crop PDF"
|
||||
},
|
||||
{
|
||||
"path": "/remove-pages",
|
||||
"label": "Delete Pages from PDF"
|
||||
},
|
||||
{
|
||||
"path": "/edit-table-of-contents",
|
||||
"label": "Edit PDF Bookmarks"
|
||||
},
|
||||
{
|
||||
"path": "/change-metadata",
|
||||
"label": "Edit PDF Metadata"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-text-editor",
|
||||
"label": "Edit PDF Text"
|
||||
},
|
||||
{
|
||||
"path": "/eml-to-pdf",
|
||||
"label": "Email (EML) to PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/extract-images",
|
||||
"label": "Extract Images from PDF"
|
||||
},
|
||||
{
|
||||
"path": "/extract-pages",
|
||||
"label": "Extract PDF Pages"
|
||||
},
|
||||
{
|
||||
"path": "/file-to-pdf",
|
||||
"label": "File to PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/form-fill",
|
||||
"label": "Fill PDF Forms"
|
||||
},
|
||||
{
|
||||
"path": "/flatten",
|
||||
"label": "Flatten PDF"
|
||||
},
|
||||
{
|
||||
"path": "/html-to-pdf",
|
||||
"label": "HTML to PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/img-to-pdf",
|
||||
"label": "Image to PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/fake-scan",
|
||||
"label": "Make a PDF Look Scanned"
|
||||
},
|
||||
{
|
||||
"path": "/markdown-to-pdf",
|
||||
"label": "Markdown to PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/merge",
|
||||
"label": "Merge PDF"
|
||||
},
|
||||
{
|
||||
"path": "/ocr",
|
||||
"label": "OCR PDF"
|
||||
},
|
||||
{
|
||||
"path": "/reorganize-pages",
|
||||
"label": "Organize PDF Pages"
|
||||
},
|
||||
{
|
||||
"path": "/overlay-pdfs",
|
||||
"label": "Overlay PDFs"
|
||||
},
|
||||
{
|
||||
"path": "/add-password",
|
||||
"label": "Password Protect PDF"
|
||||
},
|
||||
{
|
||||
"path": "/convert",
|
||||
"label": "PDF Converter"
|
||||
},
|
||||
{
|
||||
"path": "/get-pdf-info",
|
||||
"label": "PDF Info and Metadata Viewer"
|
||||
},
|
||||
{
|
||||
"path": "/page-layout",
|
||||
"label": "PDF Multi-Page Layout"
|
||||
},
|
||||
{
|
||||
"path": "/multi-tool",
|
||||
"label": "PDF Page Editor"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-cbr",
|
||||
"label": "PDF to CBR Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-cbz",
|
||||
"label": "PDF to CBZ Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-csv",
|
||||
"label": "PDF to CSV Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-xlsx",
|
||||
"label": "PDF to Excel Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-html",
|
||||
"label": "PDF to HTML Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-img",
|
||||
"label": "PDF to Image Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-markdown",
|
||||
"label": "PDF to Markdown Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-pdfa",
|
||||
"label": "PDF to PDF/A Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-presentation",
|
||||
"label": "PDF to PowerPoint Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-text",
|
||||
"label": "PDF to Text Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-word",
|
||||
"label": "PDF to Word Converter"
|
||||
},
|
||||
{
|
||||
"path": "/pdf-to-xml",
|
||||
"label": "PDF to XML Converter"
|
||||
},
|
||||
{
|
||||
"path": "/read",
|
||||
"label": "PDF Viewer"
|
||||
},
|
||||
{
|
||||
"path": "/automate",
|
||||
"label": "PDF Workflow Automation"
|
||||
},
|
||||
{
|
||||
"path": "/redact",
|
||||
"label": "Redact PDF"
|
||||
},
|
||||
{
|
||||
"path": "/remove-annotations",
|
||||
"label": "Remove Annotations from PDF"
|
||||
},
|
||||
{
|
||||
"path": "/remove-blanks",
|
||||
"label": "Remove Blank Pages from PDF"
|
||||
},
|
||||
{
|
||||
"path": "/remove-image",
|
||||
"label": "Remove Images from PDF"
|
||||
},
|
||||
{
|
||||
"path": "/remove-certificate-sign",
|
||||
"label": "Remove PDF Certificate Signature"
|
||||
},
|
||||
{
|
||||
"path": "/remove-password",
|
||||
"label": "Remove PDF Password"
|
||||
},
|
||||
{
|
||||
"path": "/repair",
|
||||
"label": "Repair PDF"
|
||||
},
|
||||
{
|
||||
"path": "/replace-color-pdf",
|
||||
"label": "Replace and Invert PDF Colors"
|
||||
},
|
||||
{
|
||||
"path": "/scale-pages",
|
||||
"label": "Resize PDF Pages"
|
||||
},
|
||||
{
|
||||
"path": "/rotate",
|
||||
"label": "Rotate PDF"
|
||||
},
|
||||
{
|
||||
"path": "/sanitize",
|
||||
"label": "Sanitize PDF"
|
||||
},
|
||||
{
|
||||
"path": "/shared-sign",
|
||||
"label": "Shared Signing"
|
||||
},
|
||||
{
|
||||
"path": "/show-js",
|
||||
"label": "Show JavaScript in PDF"
|
||||
},
|
||||
{
|
||||
"path": "/sign",
|
||||
"label": "Sign PDF"
|
||||
},
|
||||
{
|
||||
"path": "/cert-sign",
|
||||
"label": "Sign PDF with a Certificate"
|
||||
},
|
||||
{
|
||||
"path": "/split",
|
||||
"label": "Split PDF"
|
||||
},
|
||||
{
|
||||
"path": "/scanner-image-split",
|
||||
"label": "Split Scanned Photos"
|
||||
},
|
||||
{
|
||||
"path": "/timestamp-pdf",
|
||||
"label": "Timestamp PDF"
|
||||
},
|
||||
{
|
||||
"path": "/unlock-pdf-forms",
|
||||
"label": "Unlock PDF Forms"
|
||||
},
|
||||
{
|
||||
"path": "/validate-signature",
|
||||
"label": "Validate PDF Signature"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// strings) plus the actual images in public/og_images.
|
||||
//
|
||||
// Outputs:
|
||||
// src/core/data/ogImageMap.json - { toolId: imageBasename } (imported by the client)
|
||||
// public/og-metadata.json - { default, byTool, byPath } (read by the backend at startup)
|
||||
// src/core/data/ogImageMap.json - { toolId: imageBasename } (imported by the client)
|
||||
// src/core/data/urlSeoOverrides.json - { path: { title, description } } (client per-URL meta)
|
||||
// public/og-metadata.json - { default, byTool, byPath, navLinks } (read by the build-time OG prerender)
|
||||
//
|
||||
// Run: `node scripts/generate-og-metadata.mjs` (writes files)
|
||||
// `node scripts/generate-og-metadata.mjs --check` (CI drift guard: fails if stale)
|
||||
@@ -131,6 +132,69 @@ const CONVERT_SEO_PAGES = {
|
||||
},
|
||||
};
|
||||
|
||||
// Keyword titles for base tools. The in-app label ("Compress") is the UI name,
|
||||
// not the phrase people search ("Compress PDF"). Keyed by tool id; overrides the
|
||||
// prerendered <title>, the crawlable H1, and the nav-hub label, and is fed to the
|
||||
// running SPA (per alias) so the hydrated title matches. The in-app UI name is
|
||||
// unaffected - it comes from the translation registry, not this file. The tool's
|
||||
// own description is kept. Convert aliases are handled by CONVERT_SEO_PAGES.
|
||||
const TOOL_SEO_TITLES = {
|
||||
compress: "Compress PDF",
|
||||
merge: "Merge PDF",
|
||||
split: "Split PDF",
|
||||
rotate: "Rotate PDF",
|
||||
convert: "PDF Converter",
|
||||
ocr: "OCR PDF",
|
||||
watermark: "Add Watermark to PDF",
|
||||
sign: "Sign PDF",
|
||||
certSign: "Sign PDF with a Certificate",
|
||||
redact: "Redact PDF",
|
||||
crop: "Crop PDF",
|
||||
flatten: "Flatten PDF",
|
||||
repair: "Repair PDF",
|
||||
compare: "Compare PDF Files",
|
||||
sanitize: "Sanitize PDF",
|
||||
addPassword: "Password Protect PDF",
|
||||
removePassword: "Remove PDF Password",
|
||||
changePermissions: "Change PDF Permissions",
|
||||
addPageNumbers: "Add Page Numbers to PDF",
|
||||
addText: "Add Text to PDF",
|
||||
addImage: "Add Image to PDF",
|
||||
addStamp: "Add Stamp to PDF",
|
||||
addAttachments: "Add Attachments to PDF",
|
||||
extractPages: "Extract PDF Pages",
|
||||
removePages: "Delete Pages from PDF",
|
||||
removeBlanks: "Remove Blank Pages from PDF",
|
||||
removeImage: "Remove Images from PDF",
|
||||
removeAnnotations: "Remove Annotations from PDF",
|
||||
reorganizePages: "Organize PDF Pages",
|
||||
extractImages: "Extract Images from PDF",
|
||||
pageLayout: "PDF Multi-Page Layout",
|
||||
scalePages: "Resize PDF Pages",
|
||||
pdfToSinglePage: "Combine PDF into a Single Page",
|
||||
adjustContrast: "Adjust PDF Colors and Contrast",
|
||||
replaceColor: "Replace and Invert PDF Colors",
|
||||
changeMetadata: "Edit PDF Metadata",
|
||||
getPdfInfo: "PDF Info and Metadata Viewer",
|
||||
overlayPdfs: "Overlay PDFs",
|
||||
editTableOfContents: "Edit PDF Bookmarks",
|
||||
pdfTextEditor: "Edit PDF Text",
|
||||
formFill: "Fill PDF Forms",
|
||||
multiTool: "PDF Page Editor",
|
||||
read: "PDF Viewer",
|
||||
annotate: "Annotate PDF",
|
||||
automate: "PDF Workflow Automation",
|
||||
scannerEffect: "Make a PDF Look Scanned",
|
||||
scannerImageSplit: "Split Scanned Photos",
|
||||
autoRename: "Auto Rename PDF Files",
|
||||
bookletImposition: "Create a PDF Booklet",
|
||||
unlockPDFForms: "Unlock PDF Forms",
|
||||
removeCertSign: "Remove PDF Certificate Signature",
|
||||
validateSignature: "Validate PDF Signature",
|
||||
timestampPdf: "Timestamp PDF",
|
||||
showJS: "Show JavaScript in PDF",
|
||||
};
|
||||
|
||||
// Tools whose art exists under a legacy v1 filename that does not match the
|
||||
// tool id or any current URL slug. Verified against public/og_images contents.
|
||||
const LEGACY_IMAGE_OVERRIDES = {
|
||||
@@ -230,6 +294,15 @@ for (const id of allIds) {
|
||||
};
|
||||
}
|
||||
|
||||
// Upgrade base-tool titles to their search phrases (see TOOL_SEO_TITLES).
|
||||
for (const id of Object.keys(TOOL_SEO_TITLES)) {
|
||||
if (!byTool[id]) {
|
||||
console.warn(`[og] TOOL_SEO_TITLES: unknown tool id "${id}" - skipped`);
|
||||
continue;
|
||||
}
|
||||
byTool[id].title = `${TOOL_SEO_TITLES[id]} - ${SITE_NAME}`;
|
||||
}
|
||||
|
||||
// path -> toolId for every canonical path and every alias
|
||||
const byPath = {};
|
||||
for (const id of allIds) byPath[canonicalPath(id)] = id;
|
||||
@@ -297,6 +370,47 @@ for (const [routePath, seo] of Object.entries(CONVERT_SEO_PAGES)) {
|
||||
};
|
||||
}
|
||||
|
||||
// Base-tool keyword copy for the running SPA, on every alias of the tool, so the
|
||||
// hydrated <title> matches the prerendered one. Convert pages set above win (they
|
||||
// have more specific per-alias keywords), hence the guard.
|
||||
for (const [id, name] of Object.entries(TOOL_SEO_TITLES)) {
|
||||
if (!byTool[id]) continue;
|
||||
for (const p of [canonicalPath(id), ...(aliasesByTool[id] || [])])
|
||||
if (!(p in urlSeoOverrides))
|
||||
urlSeoOverrides[p] = { title: name, description: descFor(id) };
|
||||
}
|
||||
|
||||
// --- crawlable internal-link hub --------------------------------------------
|
||||
// One routable link per tool + each convert SEO landing page. The in-app
|
||||
// toolbar uses onClick buttons (not <a href>), and the prerendered body is
|
||||
// otherwise empty, so these baked links are the only tool links a crawler can
|
||||
// follow - they spread link equity and help discovery. Paths are the first
|
||||
// alias for each tool (guaranteed routable by parseToolRoute). Dev/link tools
|
||||
// are external redirects, not landing pages, so they are excluded.
|
||||
const stripSuffix = (t) =>
|
||||
t.endsWith(` - ${SITE_NAME}`) ? t.slice(0, -` - ${SITE_NAME}`.length) : t;
|
||||
const primaryPathByTool = {};
|
||||
for (const [p, id] of Object.entries(urlToTool))
|
||||
if (!(id in primaryPathByTool)) primaryPathByTool[id] = p;
|
||||
|
||||
const navLinks = [];
|
||||
const seenNav = new Set();
|
||||
const pushNav = (p, label) => {
|
||||
if (p && !seenNav.has(p)) {
|
||||
seenNav.add(p);
|
||||
navLinks.push({ path: p, label });
|
||||
}
|
||||
};
|
||||
for (const id of allIds) {
|
||||
if (linkIds.includes(id)) continue;
|
||||
pushNav(
|
||||
primaryPathByTool[id] || canonicalPath(id),
|
||||
stripSuffix(byTool[id].title),
|
||||
);
|
||||
}
|
||||
for (const [p, seo] of Object.entries(CONVERT_SEO_PAGES)) pushNav(p, seo.name);
|
||||
navLinks.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const manifest = {
|
||||
default: {
|
||||
image: `/og_images/${DEFAULT_IMAGE_BASENAME}.png`,
|
||||
@@ -305,6 +419,7 @@ const manifest = {
|
||||
},
|
||||
byTool,
|
||||
byPath,
|
||||
navLinks,
|
||||
};
|
||||
|
||||
const mapJson = JSON.stringify(ogImageMap, null, 2) + "\n";
|
||||
|
||||
@@ -14,18 +14,25 @@ export interface OgInjectOptions {
|
||||
noindex?: boolean;
|
||||
siteRoot?: string | null;
|
||||
isHome?: boolean;
|
||||
pathPrefix?: string;
|
||||
}
|
||||
|
||||
export interface OgNavLink {
|
||||
path: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface OgManifest {
|
||||
default: OgEntry;
|
||||
byTool: Record<string, OgEntry>;
|
||||
byPath: Record<string, string>;
|
||||
navLinks?: OgNavLink[];
|
||||
}
|
||||
|
||||
export function escapeHtml(value: string): string;
|
||||
export function buildOgTags(
|
||||
entry: OgEntry,
|
||||
opts?: { ogBase?: string; pageUrlPath?: string | null },
|
||||
opts?: { ogBase?: string; pageUrlPath?: string | null; pathPrefix?: string },
|
||||
): string;
|
||||
export function buildRobotsTag(noindex: boolean): string;
|
||||
export function buildCanonicalTag(canonicalUrl: string | null): string | null;
|
||||
@@ -33,6 +40,11 @@ export function buildJsonLd(
|
||||
entry: OgEntry,
|
||||
opts: { siteRoot: string; pageUrl: string; isHome: boolean },
|
||||
): string;
|
||||
export function buildBodyContent(
|
||||
entry: OgEntry,
|
||||
opts?: { navLinks?: OgNavLink[]; heading?: string | null },
|
||||
): string;
|
||||
export function injectBody(html: string, content: string): string;
|
||||
export function injectOg(
|
||||
html: string,
|
||||
entry: OgEntry,
|
||||
|
||||
@@ -25,12 +25,17 @@ const absolute = (urlPath, ogBase) => (ogBase ? ogBase + urlPath : urlPath);
|
||||
/**
|
||||
* Build the OG/Twitter <meta> block for one route.
|
||||
* @param {{image:string,title:string,description:string}} entry
|
||||
* @param {{ogBase?:string, pageUrlPath?:string|null}} opts
|
||||
* @param {{ogBase?:string, pageUrlPath?:string|null, pathPrefix?:string}} opts
|
||||
*/
|
||||
export function buildOgTags(entry, { ogBase = "", pageUrlPath = null } = {}) {
|
||||
export function buildOgTags(
|
||||
entry,
|
||||
{ ogBase = "", pageUrlPath = null, pathPrefix = "" } = {},
|
||||
) {
|
||||
const title = escapeHtml(entry.title);
|
||||
const description = escapeHtml(entry.description);
|
||||
const imageUrl = absolute(entry.image, ogBase);
|
||||
// entry.image is root-relative (/og_images/x.png); the assets deploy under the
|
||||
// sub-path too, so the absolute form must carry pathPrefix like canonical/logo.
|
||||
const imageUrl = ogBase ? ogBase + pathPrefix + entry.image : entry.image;
|
||||
const image = escapeHtml(imageUrl);
|
||||
const pageUrl = pageUrlPath
|
||||
? escapeHtml(absolute(pageUrlPath, ogBase))
|
||||
@@ -135,7 +140,7 @@ export function buildJsonLd(entry, { siteRoot, pageUrl, isHome }) {
|
||||
* link plus JSON-LD structured data.
|
||||
* @param {object} entry
|
||||
* @param {{ogBase?:string, pageUrlPath?:string|null, canonicalPath?:string|null,
|
||||
* noindex?:boolean, siteRoot?:string|null, isHome?:boolean}} opts
|
||||
* noindex?:boolean, siteRoot?:string|null, isHome?:boolean, pathPrefix?:string}} opts
|
||||
*/
|
||||
export function injectOg(html, entry, opts = {}) {
|
||||
const {
|
||||
@@ -145,13 +150,14 @@ export function injectOg(html, entry, opts = {}) {
|
||||
noindex = false,
|
||||
siteRoot = null,
|
||||
isHome = false,
|
||||
pathPrefix = "",
|
||||
} = opts;
|
||||
const canonicalUrl = ogBase
|
||||
? absolute(canonicalPath ?? pageUrlPath ?? "/", ogBase)
|
||||
: null;
|
||||
const resolvedSiteRoot = siteRoot ?? (ogBase ? `${ogBase}/` : "/");
|
||||
const blocks = [
|
||||
buildOgTags(entry, { ogBase, pageUrlPath }),
|
||||
buildOgTags(entry, { ogBase, pageUrlPath, pathPrefix }),
|
||||
buildRobotsTag(noindex),
|
||||
buildCanonicalTag(canonicalUrl),
|
||||
ogBase
|
||||
@@ -176,6 +182,62 @@ export function injectOg(html, entry, opts = {}) {
|
||||
.replace("</head>", ` ${head}</head>`);
|
||||
}
|
||||
|
||||
// Scoped styling for the prerendered body content so the pre-hydration paint
|
||||
// (what crawlers see and the first visible paint for users) looks intentional.
|
||||
// It lives inside #root and is wiped when React mounts.
|
||||
const SEO_STYLE =
|
||||
"<style>.spdf-seo{max-width:760px;margin:0 auto;padding:56px 24px;" +
|
||||
"font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#1a1a1a}" +
|
||||
".spdf-seo h1{font-size:2rem;line-height:1.2;margin:0 0 .75rem}" +
|
||||
".spdf-seo p{font-size:1.05rem;line-height:1.6;color:#444;margin:0 0 1rem}" +
|
||||
".spdf-seo nav{margin-top:2.5rem}.spdf-seo nav h2{font-size:.95rem;" +
|
||||
"text-transform:uppercase;letter-spacing:.04em;color:#666;margin:0 0 .5rem}" +
|
||||
".spdf-seo nav a{display:inline-block;margin:0 .75rem .35rem 0;" +
|
||||
"color:#0a58ca;text-decoration:none;font-size:.92rem}" +
|
||||
"@media(prefers-color-scheme:dark){.spdf-seo{color:#e8e8e8}" +
|
||||
".spdf-seo p{color:#b8b8b8}.spdf-seo nav a{color:#6ea8fe}}</style>";
|
||||
|
||||
/**
|
||||
* Build the crawlable body content injected into #root: an H1, the page
|
||||
* description, and a hub of real <a href> links to every tool. React (createRoot)
|
||||
* replaces it on mount, so it only ever shows on a full page load - i.e. to
|
||||
* crawlers and as the first paint (which also helps LCP). Links are relative so
|
||||
* they resolve against <base href> under any deploy (root, /app, context path).
|
||||
* @param {{title:string,description:string}} entry
|
||||
* @param {{navLinks?:{path:string,label:string}[], heading?:string|null}} opts
|
||||
*/
|
||||
export function buildBodyContent(
|
||||
entry,
|
||||
{ navLinks = [], heading = null } = {},
|
||||
) {
|
||||
const name =
|
||||
heading ||
|
||||
(entry.title.endsWith(APP_SUFFIX)
|
||||
? entry.title.slice(0, -APP_SUFFIX.length)
|
||||
: entry.title);
|
||||
const links = navLinks
|
||||
.map(
|
||||
(l) =>
|
||||
`<a href="${escapeHtml(l.path.replace(/^\//, ""))}">${escapeHtml(l.label)}</a>`,
|
||||
)
|
||||
.join("\n ");
|
||||
const nav = links
|
||||
? `\n <nav aria-label="All PDF tools">\n <h2>All PDF tools</h2>\n ${links}\n </nav>`
|
||||
: "";
|
||||
return (
|
||||
`<div class="spdf-seo">${SEO_STYLE}\n ` +
|
||||
`<h1>${escapeHtml(name)}</h1>\n ` +
|
||||
`<p>${escapeHtml(entry.description)}</p>${nav}\n </div>`
|
||||
);
|
||||
}
|
||||
|
||||
const ROOT_RE = /<div id="root">\s*<\/div>/;
|
||||
|
||||
/** Inject crawlable content into the (otherwise empty) React mount point. */
|
||||
export function injectBody(html, content) {
|
||||
return html.replace(ROOT_RE, `<div id="root">${content}</div>`);
|
||||
}
|
||||
|
||||
const BASE_HREF_RE = /<base\s+href="[^"]*"\s*\/?>/i;
|
||||
|
||||
// Clean single/multi-segment route (e.g. /compress, /settings/people); rejects
|
||||
@@ -210,17 +272,25 @@ export async function prerenderOg({
|
||||
const pathPrefix = baseHref.replace(/\/+$/, ""); // "" or "/app"
|
||||
const siteRoot = ogBase ? `${ogBase}${pathPrefix}/` : "/";
|
||||
const homePath = `${pathPrefix}/`;
|
||||
const navLinks = manifest.navLinks || [];
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "index.html"),
|
||||
injectOg(template, manifest.default, {
|
||||
ogBase,
|
||||
pageUrlPath: ogBase ? homePath : null,
|
||||
canonicalPath: homePath,
|
||||
siteRoot,
|
||||
isHome: true,
|
||||
let home = injectOg(template, manifest.default, {
|
||||
ogBase,
|
||||
pageUrlPath: ogBase ? homePath : null,
|
||||
canonicalPath: homePath,
|
||||
siteRoot,
|
||||
isHome: true,
|
||||
pathPrefix,
|
||||
});
|
||||
// Home <title> stays the brand ("Stirling PDF"); the H1 targets the keyword.
|
||||
home = injectBody(
|
||||
home,
|
||||
buildBodyContent(manifest.default, {
|
||||
navLinks,
|
||||
heading: "Free Online PDF Tools",
|
||||
}),
|
||||
);
|
||||
await fs.writeFile(path.join(distDir, "index.html"), home);
|
||||
|
||||
let count = 0;
|
||||
for (const [routePath, id] of Object.entries(manifest.byPath || {})) {
|
||||
@@ -235,7 +305,11 @@ export async function prerenderOg({
|
||||
noindex: !!entry.noindex,
|
||||
siteRoot,
|
||||
isHome: false,
|
||||
pathPrefix,
|
||||
});
|
||||
// App/auth pages (noindex) stay the bare shell - no crawlable landing copy.
|
||||
if (!entry.noindex)
|
||||
html = injectBody(html, buildBodyContent(entry, { navLinks }));
|
||||
const nested = segments.length > 1;
|
||||
if (nested)
|
||||
html = html.replace(BASE_HREF_RE, `<base href="${baseHref}" />`);
|
||||
|
||||
@@ -6,8 +6,10 @@ import { getToolOgImage } from "@app/data/ogImage";
|
||||
// Build tooling (plain ESM, node:fs only) - import the helpers for coverage.
|
||||
// eslint-disable-next-line no-restricted-imports -- build script lives outside the @app alias root
|
||||
import {
|
||||
buildBodyContent,
|
||||
buildOgTags,
|
||||
buildSitemap,
|
||||
injectBody,
|
||||
injectOg,
|
||||
prerenderOg,
|
||||
} from "../../../scripts/og-prerender.mjs";
|
||||
@@ -103,6 +105,21 @@ describe("injectOg (build-time prerender)", () => {
|
||||
);
|
||||
expect(tags).toContain("A "B" & <C>");
|
||||
});
|
||||
|
||||
it("weaves the sub-path prefix into the absolute image URL", () => {
|
||||
const tags = buildOgTags(entry, {
|
||||
ogBase: "https://stirling.com",
|
||||
pageUrlPath: "/app/compress",
|
||||
pathPrefix: "/app",
|
||||
});
|
||||
// image lives under the sub-path too, like canonical/og:url/logo
|
||||
expect(tags).toContain(
|
||||
'<meta property="og:image" content="https://stirling.com/app/og_images/compress.png" />',
|
||||
);
|
||||
expect(tags).toContain(
|
||||
'<meta name="twitter:image" content="https://stirling.com/app/og_images/compress.png" />',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("injectOg SEO extras (robots, canonical, JSON-LD)", () => {
|
||||
@@ -216,6 +233,103 @@ describe("buildSitemap", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildBodyContent + injectBody (crawlable landing content)", () => {
|
||||
const entry = {
|
||||
image: "/og_images/compress.png",
|
||||
title: "PDF to Word Converter - Stirling PDF",
|
||||
description: "Convert PDF files into editable Word documents.",
|
||||
};
|
||||
const navLinks = [
|
||||
{ path: "/compress", label: "Compress" },
|
||||
{ path: "/merge", label: "Merge" },
|
||||
];
|
||||
|
||||
it("emits an H1 (keyword, suffix stripped), the description, and relative tool links", () => {
|
||||
const body = buildBodyContent(entry, { navLinks });
|
||||
expect(body).toContain("<h1>PDF to Word Converter</h1>");
|
||||
expect(body).toContain(
|
||||
"<p>Convert PDF files into editable Word documents.</p>",
|
||||
);
|
||||
// links are relative (no leading slash) so they resolve against <base href>
|
||||
expect(body).toContain('<a href="compress">Compress</a>');
|
||||
expect(body).toContain('<a href="merge">Merge</a>');
|
||||
expect(body).not.toContain('href="/compress"');
|
||||
});
|
||||
|
||||
it("uses an explicit heading override for the H1 when given", () => {
|
||||
const body = buildBodyContent(entry, {
|
||||
navLinks,
|
||||
heading: "Free Online PDF Tools",
|
||||
});
|
||||
expect(body).toContain("<h1>Free Online PDF Tools</h1>");
|
||||
expect(body).not.toContain("<h1>PDF to Word Converter</h1>");
|
||||
});
|
||||
|
||||
it("escapes HTML in the H1/description", () => {
|
||||
const body = buildBodyContent(
|
||||
{ ...entry, title: "A & <B>", description: 'x "y"' },
|
||||
{ navLinks: [] },
|
||||
);
|
||||
expect(body).toContain("<h1>A & <B></h1>");
|
||||
expect(body).toContain("x "y"");
|
||||
});
|
||||
|
||||
it("injectBody fills the empty React mount point", () => {
|
||||
const out = injectBody(
|
||||
'<body><div id="root"></div><script></script></body>',
|
||||
"<h1>hi</h1>",
|
||||
);
|
||||
expect(out).toContain('<div id="root"><h1>hi</h1></div>');
|
||||
});
|
||||
|
||||
it("prerenderOg injects landing content on indexable pages but not noindex ones", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "og-body-"));
|
||||
await fs.writeFile(path.join(dir, "index.html"), TEMPLATE);
|
||||
const manifest = {
|
||||
default: {
|
||||
image: "/og_images/home.png",
|
||||
title: "Stirling PDF",
|
||||
description: "home",
|
||||
},
|
||||
byTool: {
|
||||
compress: {
|
||||
image: "/og_images/compress.png",
|
||||
title: "Compress - Stirling PDF",
|
||||
description: "c",
|
||||
},
|
||||
"/settings/people": {
|
||||
image: "/og_images/home.png",
|
||||
title: "People Settings - Stirling PDF",
|
||||
description: "p",
|
||||
noindex: true,
|
||||
},
|
||||
},
|
||||
byPath: {
|
||||
"/compress": "compress",
|
||||
"/settings/people": "/settings/people",
|
||||
},
|
||||
navLinks: [{ path: "/compress", label: "Compress" }],
|
||||
};
|
||||
|
||||
await prerenderOg({ distDir: dir, manifest, ogBase: "", baseHref: "/" });
|
||||
|
||||
const compress = await fs.readFile(path.join(dir, "compress.html"), "utf8");
|
||||
expect(compress).toContain('<div id="root"><div class="spdf-seo">');
|
||||
expect(compress).toContain("<h1>Compress</h1>");
|
||||
|
||||
const settings = await fs.readFile(
|
||||
path.join(dir, "settings", "people.html"),
|
||||
"utf8",
|
||||
);
|
||||
expect(settings).toContain('<div id="root"></div>'); // noindex: bare shell
|
||||
|
||||
const home = await fs.readFile(path.join(dir, "index.html"), "utf8");
|
||||
expect(home).toContain("spdf-seo");
|
||||
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("prerenderOg (flat + nested route files)", () => {
|
||||
it("writes a flat file per single-segment route and a nested file (absolute base) per sub-route", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "og-prerender-"));
|
||||
|
||||
@@ -74,5 +74,357 @@
|
||||
"/pdf-to-cbz": {
|
||||
"title": "PDF to CBZ Converter",
|
||||
"description": "Convert PDF documents into CBZ comic book archives. Free and private."
|
||||
},
|
||||
"/compress": {
|
||||
"title": "Compress PDF",
|
||||
"description": "Compress PDFs to reduce their file size."
|
||||
},
|
||||
"/compress-pdf": {
|
||||
"title": "Compress PDF",
|
||||
"description": "Compress PDFs to reduce their file size."
|
||||
},
|
||||
"/merge": {
|
||||
"title": "Merge PDF",
|
||||
"description": "Merge multiple PDFs into a single document"
|
||||
},
|
||||
"/merge-pdfs": {
|
||||
"title": "Merge PDF",
|
||||
"description": "Merge multiple PDFs into a single document"
|
||||
},
|
||||
"/split": {
|
||||
"title": "Split PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"/split-pdfs": {
|
||||
"title": "Split PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"/auto-split-pdf": {
|
||||
"title": "Split PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"/auto-size-split-pdf": {
|
||||
"title": "Split PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"/split-by-size-or-count": {
|
||||
"title": "Split PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"/split-pdf-by-sections": {
|
||||
"title": "Split PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"/split-pdf-by-chapters": {
|
||||
"title": "Split PDF",
|
||||
"description": "Split PDFs into multiple documents"
|
||||
},
|
||||
"/rotate": {
|
||||
"title": "Rotate PDF",
|
||||
"description": "Easily rotate your PDFs."
|
||||
},
|
||||
"/rotate-pdf": {
|
||||
"title": "Rotate PDF",
|
||||
"description": "Easily rotate your PDFs."
|
||||
},
|
||||
"/convert": {
|
||||
"title": "PDF Converter",
|
||||
"description": "Convert files to and from PDF format"
|
||||
},
|
||||
"/convert-pdf": {
|
||||
"title": "PDF Converter",
|
||||
"description": "Convert files to and from PDF format"
|
||||
},
|
||||
"/ocr": {
|
||||
"title": "OCR PDF",
|
||||
"description": "Extract text from scanned PDFs using Optical Character Recognition"
|
||||
},
|
||||
"/ocr-pdf": {
|
||||
"title": "OCR PDF",
|
||||
"description": "Extract text from scanned PDFs using Optical Character Recognition"
|
||||
},
|
||||
"/watermark": {
|
||||
"title": "Add Watermark to PDF",
|
||||
"description": "Add a custom watermark to your PDF document."
|
||||
},
|
||||
"/add-watermark": {
|
||||
"title": "Add Watermark to PDF",
|
||||
"description": "Add a custom watermark to your PDF document."
|
||||
},
|
||||
"/sign": {
|
||||
"title": "Sign PDF",
|
||||
"description": "Adds signature to PDF by drawing, text or image"
|
||||
},
|
||||
"/cert-sign": {
|
||||
"title": "Sign PDF with a Certificate",
|
||||
"description": "Sign PDF documents using digital certificates"
|
||||
},
|
||||
"/manage-signatures": {
|
||||
"title": "Sign PDF with a Certificate",
|
||||
"description": "Sign PDF documents using digital certificates"
|
||||
},
|
||||
"/redact": {
|
||||
"title": "Redact PDF",
|
||||
"description": "Permanently remove sensitive information from PDF documents"
|
||||
},
|
||||
"/auto-redact": {
|
||||
"title": "Redact PDF",
|
||||
"description": "Permanently remove sensitive information from PDF documents"
|
||||
},
|
||||
"/crop": {
|
||||
"title": "Crop PDF",
|
||||
"description": "Crop a PDF to reduce its size (maintains text!)"
|
||||
},
|
||||
"/flatten": {
|
||||
"title": "Flatten PDF",
|
||||
"description": "Remove all interactive elements and forms from a PDF"
|
||||
},
|
||||
"/repair": {
|
||||
"title": "Repair PDF",
|
||||
"description": "Repair corrupted or damaged PDF files"
|
||||
},
|
||||
"/compare": {
|
||||
"title": "Compare PDF Files",
|
||||
"description": "Compare two PDF documents and highlight differences"
|
||||
},
|
||||
"/sanitize": {
|
||||
"title": "Sanitize PDF",
|
||||
"description": "Remove potentially harmful elements from PDF files"
|
||||
},
|
||||
"/sanitize-pdf": {
|
||||
"title": "Sanitize PDF",
|
||||
"description": "Remove potentially harmful elements from PDF files"
|
||||
},
|
||||
"/add-password": {
|
||||
"title": "Password Protect PDF",
|
||||
"description": "Add password protection and restrictions to PDF files"
|
||||
},
|
||||
"/remove-password": {
|
||||
"title": "Remove PDF Password",
|
||||
"description": "Remove password protection from PDF documents"
|
||||
},
|
||||
"/change-permissions": {
|
||||
"title": "Change PDF Permissions",
|
||||
"description": "Change document restrictions and permissions"
|
||||
},
|
||||
"/add-page-numbers": {
|
||||
"title": "Add Page Numbers to PDF",
|
||||
"description": "Add Page numbers throughout a document in a set location"
|
||||
},
|
||||
"/add-text": {
|
||||
"title": "Add Text to PDF",
|
||||
"description": "Add custom text anywhere in your PDF"
|
||||
},
|
||||
"/add-image": {
|
||||
"title": "Add Image to PDF",
|
||||
"description": "Add images anywhere in your PDF"
|
||||
},
|
||||
"/add-stamp": {
|
||||
"title": "Add Stamp to PDF",
|
||||
"description": "Add text or add image stamps at set locations"
|
||||
},
|
||||
"/stamp": {
|
||||
"title": "Add Stamp to PDF",
|
||||
"description": "Add text or add image stamps at set locations"
|
||||
},
|
||||
"/add-attachments": {
|
||||
"title": "Add Attachments to PDF",
|
||||
"description": "Add or remove embedded files (attachments) to/from a PDF"
|
||||
},
|
||||
"/extract-pages": {
|
||||
"title": "Extract PDF Pages",
|
||||
"description": "Extract specific pages from a PDF document"
|
||||
},
|
||||
"/extract-page": {
|
||||
"title": "Extract PDF Pages",
|
||||
"description": "Extract specific pages from a PDF document"
|
||||
},
|
||||
"/remove-pages": {
|
||||
"title": "Delete Pages from PDF",
|
||||
"description": "Remove specific pages from a PDF document"
|
||||
},
|
||||
"/remove-blanks": {
|
||||
"title": "Remove Blank Pages from PDF",
|
||||
"description": "Remove blank pages from PDF documents"
|
||||
},
|
||||
"/remove-image": {
|
||||
"title": "Remove Images from PDF",
|
||||
"description": "Remove all images from a PDF document"
|
||||
},
|
||||
"/remove-image-pdf": {
|
||||
"title": "Remove Images from PDF",
|
||||
"description": "Remove all images from a PDF document"
|
||||
},
|
||||
"/remove-annotations": {
|
||||
"title": "Remove Annotations from PDF",
|
||||
"description": "Remove annotations and comments from PDF documents"
|
||||
},
|
||||
"/reorganize-pages": {
|
||||
"title": "Organize PDF Pages",
|
||||
"description": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||
},
|
||||
"/pdf-organizer": {
|
||||
"title": "Organize PDF Pages",
|
||||
"description": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||
},
|
||||
"/extract-images": {
|
||||
"title": "Extract Images from PDF",
|
||||
"description": "Extract images from PDF documents"
|
||||
},
|
||||
"/page-layout": {
|
||||
"title": "PDF Multi-Page Layout",
|
||||
"description": "Merge multiple pages of a PDF document into a single page"
|
||||
},
|
||||
"/multi-page-layout": {
|
||||
"title": "PDF Multi-Page Layout",
|
||||
"description": "Merge multiple pages of a PDF document into a single page"
|
||||
},
|
||||
"/scale-pages": {
|
||||
"title": "Resize PDF Pages",
|
||||
"description": "Change the size/scale of a page and/or its contents."
|
||||
},
|
||||
"/pdf-to-single-page": {
|
||||
"title": "Combine PDF into a Single Page",
|
||||
"description": "Merges all PDF pages into one large single page"
|
||||
},
|
||||
"/single-large-page": {
|
||||
"title": "Combine PDF into a Single Page",
|
||||
"description": "Merges all PDF pages into one large single page"
|
||||
},
|
||||
"/adjust-contrast": {
|
||||
"title": "Adjust PDF Colors and Contrast",
|
||||
"description": "Adjust colors and contrast of PDF documents"
|
||||
},
|
||||
"/replace-color": {
|
||||
"title": "Replace and Invert PDF Colors",
|
||||
"description": "Replace or invert colors in PDF documents"
|
||||
},
|
||||
"/replace-color-pdf": {
|
||||
"title": "Replace and Invert PDF Colors",
|
||||
"description": "Replace or invert colors in PDF documents"
|
||||
},
|
||||
"/replace-and-invert-color-pdf": {
|
||||
"title": "Replace and Invert PDF Colors",
|
||||
"description": "Replace or invert colors in PDF documents"
|
||||
},
|
||||
"/change-metadata": {
|
||||
"title": "Edit PDF Metadata",
|
||||
"description": "Change/Remove/Add metadata from a PDF document"
|
||||
},
|
||||
"/get-pdf-info": {
|
||||
"title": "PDF Info and Metadata Viewer",
|
||||
"description": "Grabs any and all information possible on PDFs"
|
||||
},
|
||||
"/get-info-on-pdf": {
|
||||
"title": "PDF Info and Metadata Viewer",
|
||||
"description": "Grabs any and all information possible on PDFs"
|
||||
},
|
||||
"/overlay-pdfs": {
|
||||
"title": "Overlay PDFs",
|
||||
"description": "The Free Adobe Acrobat alternative (10M+ Downloads)"
|
||||
},
|
||||
"/overlay-pdf": {
|
||||
"title": "Overlay PDFs",
|
||||
"description": "The Free Adobe Acrobat alternative (10M+ Downloads)"
|
||||
},
|
||||
"/edit-table-of-contents": {
|
||||
"title": "Edit PDF Bookmarks",
|
||||
"description": "Add or edit bookmarks and table of contents in PDF documents"
|
||||
},
|
||||
"/pdf-text-editor": {
|
||||
"title": "Edit PDF Text",
|
||||
"description": "Review and edit text and images in PDFs with grouped text editing and PDF regeneration"
|
||||
},
|
||||
"/form-fill": {
|
||||
"title": "Fill PDF Forms",
|
||||
"description": "Fill PDF form fields interactively with a visual editor"
|
||||
},
|
||||
"/multi-tool": {
|
||||
"title": "PDF Page Editor",
|
||||
"description": "Use multiple tools on a single PDF document"
|
||||
},
|
||||
"/read": {
|
||||
"title": "PDF Viewer",
|
||||
"description": "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
||||
},
|
||||
"/view-pdf": {
|
||||
"title": "PDF Viewer",
|
||||
"description": "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
||||
},
|
||||
"/annotate": {
|
||||
"title": "Annotate PDF",
|
||||
"description": "Highlight, draw, add notes, and shapes directly in the viewer"
|
||||
},
|
||||
"/annotations": {
|
||||
"title": "Annotate PDF",
|
||||
"description": "Highlight, draw, add notes, and shapes directly in the viewer"
|
||||
},
|
||||
"/automate": {
|
||||
"title": "PDF Workflow Automation",
|
||||
"description": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||
},
|
||||
"/pipeline": {
|
||||
"title": "PDF Workflow Automation",
|
||||
"description": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||
},
|
||||
"/scanner-effect": {
|
||||
"title": "Make a PDF Look Scanned",
|
||||
"description": "Create a PDF that looks like it was scanned"
|
||||
},
|
||||
"/fake-scan": {
|
||||
"title": "Make a PDF Look Scanned",
|
||||
"description": "Create a PDF that looks like it was scanned"
|
||||
},
|
||||
"/scanner-image-split": {
|
||||
"title": "Split Scanned Photos",
|
||||
"description": "Detect and split scanned photos into separate pages"
|
||||
},
|
||||
"/extract-image-scans": {
|
||||
"title": "Split Scanned Photos",
|
||||
"description": "Detect and split scanned photos into separate pages"
|
||||
},
|
||||
"/auto-rename": {
|
||||
"title": "Auto Rename PDF Files",
|
||||
"description": "Automatically rename PDF files based on their content"
|
||||
},
|
||||
"/booklet-imposition": {
|
||||
"title": "Create a PDF Booklet",
|
||||
"description": "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
||||
},
|
||||
"/unlock-p-d-f-forms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"description": "Remove read-only property of form fields in a PDF document."
|
||||
},
|
||||
"/unlock-pdf-forms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"description": "Remove read-only property of form fields in a PDF document."
|
||||
},
|
||||
"/remove-cert-sign": {
|
||||
"title": "Remove PDF Certificate Signature",
|
||||
"description": "Remove digital signature from PDF documents"
|
||||
},
|
||||
"/remove-certificate-sign": {
|
||||
"title": "Remove PDF Certificate Signature",
|
||||
"description": "Remove digital signature from PDF documents"
|
||||
},
|
||||
"/validate-signature": {
|
||||
"title": "Validate PDF Signature",
|
||||
"description": "Verify digital signatures and certificates in PDF documents"
|
||||
},
|
||||
"/timestamp-pdf": {
|
||||
"title": "Timestamp PDF",
|
||||
"description": "Add an RFC 3161 document timestamp to prove when your PDF existed"
|
||||
},
|
||||
"/show-j-s": {
|
||||
"title": "Show JavaScript in PDF",
|
||||
"description": "Extract and display JavaScript code from PDF documents"
|
||||
},
|
||||
"/show-js": {
|
||||
"title": "Show JavaScript in PDF",
|
||||
"description": "Extract and display JavaScript code from PDF documents"
|
||||
},
|
||||
"/show-javascript": {
|
||||
"title": "Show JavaScript in PDF",
|
||||
"description": "Extract and display JavaScript code from PDF documents"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user