Skip to content

Commit b6930d8

Browse files
committed
Add SEO optimizations to the book
- Add meta tags for better SEO - Create sitemap generation script - Add robots.txt file - Add custom CSS and JS for improved readability and structured data - Create build script to automate the process - Update book.toml with additional metadata and configuration Signed-off-by: Ian Ker-Seymer <hello@ianks.com>
1 parent 9c0f0fa commit b6930d8

File tree

8 files changed

+333
-0
lines changed

8 files changed

+333
-0
lines changed

book/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# The Ruby on Rust Book
2+
3+
This directory contains the source for "The Ruby on Rust Book" which is built using [mdBook](https://rust-lang.github.io/mdBook/).
4+
5+
## SEO Setup
6+
7+
This book has been configured with SEO optimizations:
8+
9+
1. **Meta Tags**: The theme has been updated to include proper meta tags for SEO, including Open Graph and Twitter card support.
10+
11+
2. **Sitemap Generation**: A custom Python script (`generate_sitemap.py`) generates a sitemap.xml file after the book is built.
12+
13+
3. **Robots.txt**: A robots.txt file is included to help search engines navigate the site.
14+
15+
4. **Custom CSS/JS**: Custom CSS and JS files are included to improve readability and add structured data for search engines.
16+
17+
## Building the Book
18+
19+
To build the book with all SEO features:
20+
21+
```bash
22+
# Navigate to the book directory
23+
cd book
24+
25+
# Run the build script
26+
./build_with_sitemap.sh
27+
```
28+
29+
This will:
30+
1. Build the book using mdBook
31+
2. Generate the sitemap.xml file
32+
3. Copy the robots.txt file to the output directory
33+
34+
## Submitting to Search Engines
35+
36+
After deploying the site, you should submit the sitemap to search engines:
37+
38+
- Google: Submit through [Google Search Console](https://search.google.com/search-console)
39+
- Bing: Submit through [Bing Webmaster Tools](https://www.bing.com/webmasters)
40+
41+
## Customizing SEO
42+
43+
To customize the SEO settings:
44+
45+
1. Edit `book.toml` to update metadata like title and description
46+
2. Modify `theme/index.hbs` to update meta tags
47+
3. Update `theme/css/custom.css` and `theme/js/custom.js` for styling and behavior
48+
4. Adjust `generate_sitemap.py` to change sitemap generation settings

book/book.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,22 @@ authors = ["Ian Ker-Seymer"]
33
language = "en"
44
src = "src"
55
title = "The Ruby on Rust Book"
6+
description = "A comprehensive guide to building Ruby extensions with Rust using rb-sys"
7+
8+
[output.html]
9+
git-repository-url = "https://github.com/oxidize-rb/rb-sys"
10+
git-repository-icon = "fa-github"
11+
site-url = "https://oxidize-rb.github.io/rb-sys/"
12+
additional-css = ["theme/css/custom.css"]
13+
additional-js = ["theme/js/custom.js"]
14+
15+
[build]
16+
extra-watch-dirs = ["theme"]
17+
18+
[preprocessor.links]
19+
20+
# Copy robots.txt to the output directory
21+
[[output.html.print.html-before-content]]
22+
text = '''
23+
<!-- This is a placeholder to ensure robots.txt is copied -->
24+
'''

book/build_with_sitemap.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
# Script to build the book and generate the sitemap
3+
4+
set -e
5+
6+
# Build the book
7+
echo "Building the book..."
8+
mdbook build
9+
10+
# Generate the sitemap
11+
echo "Generating sitemap..."
12+
python3 generate_sitemap.py
13+
14+
# Copy robots.txt to the output directory
15+
echo "Copying robots.txt to the output directory..."
16+
cp src/robots.txt html/
17+
18+
echo "Done! The book has been built and the sitemap has been generated."
19+
echo "The sitemap is available at html/sitemap.xml"

book/generate_sitemap.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate a sitemap.xml file for the mdBook site.
4+
This script should be run after the book is built.
5+
"""
6+
7+
import os
8+
import datetime
9+
import xml.dom.minidom
10+
import xml.etree.ElementTree as ET
11+
from pathlib import Path
12+
13+
# Configuration
14+
SITE_URL = "https://oxidize-rb.github.io/rb-sys" # Base URL of your site
15+
BOOK_DIR = "book" # Directory where the book is built
16+
OUTPUT_DIR = os.path.join(BOOK_DIR, "html") # Directory where the HTML files are
17+
SITEMAP_PATH = os.path.join(OUTPUT_DIR, "sitemap.xml") # Path to the sitemap.xml file
18+
CHANGE_FREQ = "weekly" # How frequently the page is likely to change
19+
PRIORITY = "0.8" # Priority of this URL relative to other URLs on your site
20+
21+
def generate_sitemap():
22+
"""Generate a sitemap.xml file for the mdBook site."""
23+
# Create the root element
24+
urlset = ET.Element("urlset", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
25+
26+
# Get the current date in the format required by sitemaps
27+
today = datetime.datetime.now().strftime("%Y-%m-%d")
28+
29+
# Walk through the output directory
30+
for root, _, files in os.walk(OUTPUT_DIR):
31+
for file in files:
32+
if file.endswith(".html") and file != "404.html" and file != "print.html":
33+
# Get the relative path from the output directory
34+
rel_path = os.path.relpath(os.path.join(root, file), OUTPUT_DIR)
35+
36+
# Convert Windows path separators to URL path separators
37+
rel_path = rel_path.replace("\\", "/")
38+
39+
# Create the URL
40+
if rel_path == "index.html":
41+
url = SITE_URL
42+
else:
43+
url = f"{SITE_URL}/{rel_path}"
44+
45+
# Create the URL element
46+
url_element = ET.SubElement(urlset, "url")
47+
loc = ET.SubElement(url_element, "loc")
48+
loc.text = url
49+
lastmod = ET.SubElement(url_element, "lastmod")
50+
lastmod.text = today
51+
changefreq = ET.SubElement(url_element, "changefreq")
52+
changefreq.text = CHANGE_FREQ
53+
priority = ET.SubElement(url_element, "priority")
54+
priority.text = PRIORITY
55+
56+
# Create the XML tree
57+
tree = ET.ElementTree(urlset)
58+
59+
# Pretty print the XML
60+
xmlstr = xml.dom.minidom.parseString(ET.tostring(urlset)).toprettyxml(indent=" ")
61+
62+
# Write the XML to the sitemap.xml file
63+
with open(SITEMAP_PATH, "w", encoding="utf-8") as f:
64+
f.write(xmlstr)
65+
66+
print(f"Sitemap generated at {SITEMAP_PATH}")
67+
68+
if __name__ == "__main__":
69+
# Make sure the output directory exists
70+
os.makedirs(OUTPUT_DIR, exist_ok=True)
71+
72+
# Generate the sitemap
73+
generate_sitemap()

book/src/robots.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
User-agent: *
2+
Allow: /
3+
4+
Sitemap: https://oxidize-rb.github.io/rb-sys/sitemap.xml

book/theme/css/custom.css

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* Custom CSS for SEO and improved readability */
2+
3+
/* Improve readability */
4+
.content {
5+
line-height: 1.6;
6+
font-size: 16px;
7+
}
8+
9+
/* Improve heading hierarchy for SEO */
10+
h1 {
11+
font-size: 2.2em;
12+
margin-top: 1.5em;
13+
margin-bottom: 0.8em;
14+
}
15+
16+
h2 {
17+
font-size: 1.8em;
18+
margin-top: 1.3em;
19+
margin-bottom: 0.7em;
20+
}
21+
22+
h3 {
23+
font-size: 1.5em;
24+
margin-top: 1.1em;
25+
margin-bottom: 0.6em;
26+
}
27+
28+
/* Improve code block readability */
29+
pre {
30+
padding: 1em;
31+
border-radius: 5px;
32+
}
33+
34+
/* Improve link visibility */
35+
a {
36+
text-decoration: none;
37+
transition: color 0.2s ease;
38+
}
39+
40+
a:hover {
41+
text-decoration: underline;
42+
}
43+
44+
/* Improve table readability */
45+
table {
46+
width: 100%;
47+
margin: 1em 0;
48+
border-collapse: collapse;
49+
}
50+
51+
table th, table td {
52+
padding: 0.5em;
53+
border: 1px solid #ddd;
54+
}
55+
56+
table th {
57+
background-color: #f5f5f5;
58+
}
59+
60+
/* Improve blockquote styling */
61+
blockquote {
62+
border-left: 4px solid #ddd;
63+
padding-left: 1em;
64+
margin-left: 0;
65+
color: #555;
66+
}
67+
68+
/* Improve image display */
69+
img {
70+
max-width: 100%;
71+
height: auto;
72+
display: block;
73+
margin: 1em auto;
74+
}
75+
76+
/* Improve print layout */
77+
@media print {
78+
.nav-chapters, .sidebar, #menu-bar, .footer-note {
79+
display: none;
80+
}
81+
82+
.page {
83+
margin: 0;
84+
padding: 0;
85+
}
86+
87+
.content {
88+
max-width: 100%;
89+
margin: 0;
90+
padding: 0;
91+
}
92+
}

book/theme/index.hbs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@
1616
{{> head}}
1717

1818
<meta name="description" content="{{ description }}">
19+
<meta name="keywords" content="ruby, rust, ffi, bindings, rb-sys, ruby on rust">
20+
<meta name="author" content="{{ author }}">
21+
22+
<!-- Open Graph / Facebook -->
23+
<meta property="og:type" content="website">
24+
<meta property="og:url" content="{{ base_url }}{{ path_to_root }}">
25+
<meta property="og:title" content="{{ title }}">
26+
<meta property="og:description" content="{{ description }}">
27+
28+
<!-- Twitter -->
29+
<meta property="twitter:card" content="summary_large_image">
30+
<meta property="twitter:url" content="{{ base_url }}{{ path_to_root }}">
31+
<meta property="twitter:title" content="{{ title }}">
32+
<meta property="twitter:description" content="{{ description }}">
33+
34+
<!-- Canonical URL -->
35+
<link rel="canonical" href="{{ base_url }}{{ path_to_root }}">
1936
<meta name="viewport" content="width=device-width, initial-scale=1">
2037
<meta name="theme-color" content="#ffffff">
2138

book/theme/js/custom.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Custom JavaScript for SEO and improved user experience
2+
3+
document.addEventListener('DOMContentLoaded', function() {
4+
// Add 'noopener' and 'noreferrer' to external links for security
5+
const externalLinks = document.querySelectorAll('a[href^="http"]');
6+
externalLinks.forEach(link => {
7+
if (!link.getAttribute('rel')) {
8+
link.setAttribute('rel', 'noopener noreferrer');
9+
}
10+
});
11+
12+
// Add title attributes to links without them for better accessibility
13+
const linksWithoutTitle = document.querySelectorAll('a:not([title])');
14+
linksWithoutTitle.forEach(link => {
15+
if (link.textContent.trim()) {
16+
link.setAttribute('title', link.textContent.trim());
17+
}
18+
});
19+
20+
// Add alt attributes to images without them for better accessibility
21+
const imagesWithoutAlt = document.querySelectorAll('img:not([alt])');
22+
imagesWithoutAlt.forEach(img => {
23+
const imgSrc = img.getAttribute('src');
24+
if (imgSrc) {
25+
const altText = imgSrc.split('/').pop().split('.')[0].replace(/[-_]/g, ' ');
26+
img.setAttribute('alt', altText);
27+
}
28+
});
29+
30+
// Add structured data for search engines
31+
const structuredData = {
32+
"@context": "https://schema.org",
33+
"@type": "TechArticle",
34+
"headline": document.title,
35+
"description": document.querySelector('meta[name="description"]')?.getAttribute('content') || "",
36+
"author": {
37+
"@type": "Person",
38+
"name": document.querySelector('meta[name="author"]')?.getAttribute('content') || "Ian Ker-Seymer"
39+
},
40+
"publisher": {
41+
"@type": "Organization",
42+
"name": "rb-sys",
43+
"logo": {
44+
"@type": "ImageObject",
45+
"url": "https://oxidize-rb.github.io/rb-sys/favicon.png"
46+
}
47+
},
48+
"url": window.location.href,
49+
"mainEntityOfPage": {
50+
"@type": "WebPage",
51+
"@id": window.location.href
52+
},
53+
"datePublished": new Date().toISOString().split('T')[0],
54+
"dateModified": new Date().toISOString().split('T')[0]
55+
};
56+
57+
const script = document.createElement('script');
58+
script.type = 'application/ld+json';
59+
script.textContent = JSON.stringify(structuredData);
60+
document.head.appendChild(script);
61+
});

0 commit comments

Comments
 (0)