Comprehensive Guide: Franklin.jl Documentation Site with Multi-Repository GitHub Pages Deployment
Executive Summary
This guide provides complete instructions for setting up a centralized documentation ecosystem using Franklin.jl as the main site generator and GitHub Pages for hosting. The architecture supports multiple code repositories (built with DrWatson.jl and Documenter.jl) that deploy their documentation as subdirectories within a unified custom domain.
What You'll Achieve:
Main Franklin.jl site at
https://study.fourm.info/
Subrepository documentation at
https://study.fourm.info/math_foundations/
, etc.Consistent styling across all documentation
Automated deployment via GitHub Actions
Single custom domain hosting multiple projects
Architecture Overview:
Franklin.jl with Lanyon Template: Handles main site content with responsive design
Documenter.jl: Generates API documentation for individual code projects
GitHub Pages: Provides free hosting with custom domain support
GitHub Actions: Automates building and deployment
Key Changes for Lanyon Template:
Use
template="lanyon"
instead oftemplate="basic"
Requires
FranklinTemplates
package in addition toFranklin
Uses
+++
syntax for page headers (YAML front matter) and@def
for in-content variablesBuilt-in responsive sidebar navigation
Mobile-first design approach
Pre-configured CSS and layout files
Table of Contents
Prerequisites and Planning
Required Tools
Julia 1.9+ installed
Git and GitHub account
Custom domain (optional but recommended)
Text editor (VS Code recommended)
Required Julia Packages
For the main Franklin.jl repository with Lanyon template:
using Pkg
Pkg.add("Franklin")
Pkg.add("FranklinTemplates") # Required for Lanyon template
For DrWatson.jl subrepositories:
using Pkg
Pkg.add("DrWatson")
Pkg.add("Documenter")
Pkg.add("DocumenterTools") # For generating deployment keys
Pkg.add("Reexport") # For convenient package re-exports
Repository Architecture
Main Repository (this one)
├── Franklin.jl site files
├── GitHub Actions workflow
└── Custom domain configuration
Subrepositories
├── DrWatson.jl project structure
├── Documenter.jl documentation
└── CI workflow for deployment
Domain Strategy
Single Domain Approach (Recommended):
One repository hosts the custom domain
All other repositories deploy to subdirectories
Avoids "custom domain already taken" errors
URL Structure:
https://study.fourm.info/
(main Franklin site)https://study.fourm.info/math_foundations/
(subproject docs)https://study.fourm.info/another_project/
(additional subprojects)
Franklin.jl Main Site Setup
Note: This guide uses the current Franklin.jl workflow (v0.10+). If you're familiar with older Franklin tutorials that reference separate
setup_franklin.jl
files, those are no longer necessary with modern Franklin.
Step 1: Initialize Franklin with Lanyon Template
First, install the required packages and initialize the site directly in the Julia REPL:
# In Julia REPL or create a simple init script
using Pkg
Pkg.add("Franklin")
Pkg.add("FranklinTemplates")
# Initialize Franklin site with Lanyon template
using Franklin, FranklinTemplates
newsite(".", template="lanyon")
Alternative method (recommended for automation): Create a one-time initialization script:
# File: init_site.jl (run once, then delete)
using Pkg
Pkg.add(["Franklin", "FranklinTemplates"])
using Franklin, FranklinTemplates
newsite(".", template="lanyon")
julia init_site.jl
rm init_site.jl # Delete after use
Note: The newsite
function will prompt you if the directory is not empty. The Lanyon template provides a complete, ready-to-use site structure.
Step 2: Project Configuration
Create or update Project.toml
:
name = "MathTechStudy"
uuid = "generate-your-uuid-here"
version = "0.1.0"
[deps]
Franklin = "713c75ef-9fc9-4b05-94a9-213340da978e"
FranklinTemplates = "45ca9322-c9b3-4ca9-9cc0-5da5e1b6fb39"
Step 3: Git Configuration
Create or update .gitignore
for Franklin projects:
# Franklin.jl generated content
__site/
# Julia package management - Choose ONE approach:
# Option A: Standard approach (commit Manifest.toml for reproducible builds)
# (No Manifest.toml line in .gitignore)
# Option B: Multi-machine development (ignore Manifest.toml to avoid conflicts)
Manifest.toml
# Operating System
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE and Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# Temporary files
*.tmp
*.temp
*.log
# Node.js (if using any Node.js tools)
node_modules/
package-lock.json
npm-debug.log*
# Documentation build artifacts (keep raw/ for backups)
raw/
Important Notes:
Always ignore
__site/
: This contains the generated HTML and should never be committedCommit
Project.toml
: Essential for package dependenciesKeep source files: All
.md
,_layout/
,_css/
,_assets/
should be committed
Manifest.toml Strategy (Choose Based on Your Workflow):
Option A - Commit Manifest.toml (Reproducible Builds):
Best for: Single-machine development, production deployments, team consistency
Workflow: Use
julia --project=. -e "using Pkg; Pkg.resolve()"
after git pull to sync versions
Option B - Ignore Manifest.toml (Multi-Machine Flexibility):
Best for: Multi-machine development, avoiding version conflicts
Trade-off: Less reproducible builds, but fewer git conflicts
Mitigation: Pin critical versions in Project.toml with exact version constraints
Option C - Hybrid Approach (Recommended):
Ignore Manifest.toml in development
Include it in CI/CD for reproducible deployments
Use Project.toml with conservative version bounds
Implementing the Hybrid Approach (Option C)
For multi-machine development with reproducible CI/CD:
Update your
.gitignore
to ignore Manifest.toml:
Manifest.toml
Pin critical versions in
Project.toml
:
[deps]
Franklin = "0.10" # Pin major.minor for stability
FranklinTemplates = "0.9"
Modify CI/CD to generate fresh Manifest.toml:
# In your GitHub Actions workflow
- name: Install Franklin dependencies
run: |
julia --project=. -e "using Pkg; Pkg.instantiate(); Pkg.resolve()"
Multi-machine workflow:
# After git pull on any machine
julia --project=. -e "using Pkg; Pkg.update()"
This approach gives you:
✅ No git conflicts between machines
✅ Reproducible CI/CD builds
✅ Automatic updates to latest compatible versions
✅ Stability through Project.toml constraints
Adapting Julia startup.jl for Multi-Machine Strategy
If you use a Julia startup.jl
file that auto-activates projects, update it for the hybrid approach:
Before (requires both files):
if isfile("Project.toml") && isfile("Manifest.toml")
quickactivate(".")
end
After (works with hybrid strategy):
# activate project (multi-machine strategy)
if isfile("Project.toml")
quickactivate(".")
# Optional: Auto-resolve for multi-machine development
# Uncomment if you want automatic resolution on startup
if haskey(ENV, "JULIA_AUTO_RESOLVE")
println("🔄 Auto-resolving packages...")
using Pkg; Pkg.resolve()
end
end
Manual resolution workflow:
# Enable auto-resolve (any value works)
export JULIA_AUTO_RESOLVE=1
julia # Will auto-resolve on startup
# Disable auto-resolve
unset JULIA_AUTO_RESOLVE
julia # Fast startup, no resolution
# Or resolve manually when needed
julia --project=. -e "using Pkg; Pkg.resolve()"
Key Simplification: The above uses haskey(ENV, "JULIA_AUTO_RESOLVE")
instead of complex boolean logic. This is much cleaner than checking file existence AND environment variables with OR conditions - just set the variable when you want auto-resolution, don't set it when you want fast startup.
Step 4: Franklin Configuration for Lanyon
The Lanyon template uses config.md
for global settings. Create/update it with:
+++
# Website info
website_title = "Study Guides"
website_descr = "Comprehensive study guides and documentation"
website_url = "https://study.fourm.info/"
author = "Your Name"
# Franklin/Lanyon settings
base_url_prefix = "https://study.fourm.info" # For custom domain
prepath = "" # Leave empty when using custom domain
# Build settings
minify = true
ignore = ["node_modules/", "franklin", "franklin.pub", ".github/", "__site/"]
# Content settings
mintoclevel = 2
maxtoclevel = 3
hasmath = false # Set to true if you need math rendering globally
hascode = true # Enable code syntax highlighting
+++
<!-- Example: Franklin global page defaults (these would go in +++config+++ block) -->
<!-- @def title = "Study Guides" -->
<!-- @def hasmath = false -->
<!-- @def hascode = true -->
<!-- Global Franklin commands -->
\newcommand{\note}[1]{@@note @@title ⚠ Note @@ @@content #1 @@ @@}
\newcommand{\warn}[1]{@@warning @@title ⚠ Warning @@ @@content #1 @@ @@}
Note: Unlike Jekyll, Franklin.jl uses config.md
(not _config.yml
) and processes both YAML front matter (+++
) and Franklin commands in the same file.
Step 4: Create Main Landing Page for Lanyon
Create index.md
:
+++
title = "Study Guides Home"
tags = ["homepage", "guides"]
+++
# Study Guides
Welcome to the comprehensive study guides and documentation repository.
## Available Content
### Setup & Deployment Guides
- [Franklin.jl Setup Guide](/franklin-setup/)
- [GitHub Pages Configuration](/github-pages-setup/)
### Code Project Documentation
~~~
<div class="posts">
<div class="post">
<h3 class="post-title">
<a href="/math_foundations/">Math Foundations</a>
</h3>
<span class="post-date">Project Documentation</span>
<p>Advanced mathematical foundations and algorithms</p>
</div>
<div class="post">
<h3 class="post-title">
<a href="/projects/">Additional Projects</a>
</h3>
<span class="post-date">Coming Soon</span>
<p>More project documentation coming soon</p>
</div>
</div>
~~~
### Quick Start Guides
- [Getting Started with Julia](/julia-quickstart/)
- [Setting Up Development Environment](/dev-setup/)
## Navigation
The Lanyon template provides a collapsible sidebar for navigation. Use it to navigate between different sections, or explore the subdirectories for detailed project documentation.
\toc
Step 5: Convert Existing Guides for Franklin/Lanyon
For each existing markdown file, add Franklin metadata using +++
syntax for page headers:
+++
title = "Your Guide Title"
hascode = true
date = Date(2025, 6, 14)
rss = "Brief description for RSS feeds"
tags = ["relevant", "tags", "here"]
+++
# Your Guide Title
\toc
<!-- Your existing content here -->
<!-- Example: Use @def for in-content variables if needed -->
<!-- @def author = "Your Name" -->
<!-- @def version = "1.0" -->
<!-- Franklin-specific commands work in Lanyon -->
\note{This is a note using a custom Franklin command}
\warn{This is a warning using a custom Franklin command}
Franklin/Lanyon syntax clarification:
Page Headers: Use
+++
syntax for title, tags, date, and other metadata (YAML front matter)In-Content Variables: Use
@def variable = value
for variables referenced within the page contentVariable References: Use
{{fill variable}}
to reference - Links: Use/page-name/
for internal links (with trailing slash)Math: Use
$inline math$
and$$display math$$
Code: Use standard markdown code fences with language specification
Images: Place in
/assets/
folder and reference as/assets/image.jpg
Table of Contents: Use
\toc
command
Important Note:
+++
(YAML front matter) = Page metadata that Franklin processes before rendering@def
= Franklin variables that can be referenced and filled within page contentBoth syntaxes work together:
+++
for page setup,@def
for dynamic content
Lanyon Template Features
Understanding Lanyon
Lanyon is a responsive Jekyll-inspired theme for Franklin.jl that provides:
Responsive sidebar navigation that collapses on mobile devices
Clean, minimal design focused on content readability
Built-in support for code highlighting and math rendering
Customizable color themes and typography
Mobile-first approach with touch-friendly navigation
Lanyon File Structure
After running newsite(".", template="lanyon")
, you'll have:
.
├── _layout/ # Template files (HTML layouts)
├── _css/ # Stylesheets (CSS and SCSS)
├── _libs/ # JavaScript libraries
├── assets/ # Images and other static assets
├── config.md # Global site configuration
├── index.md # Homepage content
├── utils.jl # Utility functions for building
└── Project.toml # Julia project dependencies
Customizing Lanyon
Sidebar Configuration
Edit config.md
to customize the sidebar:
+++
website_title = "Study Guides"
website_title_short = "Study Guides" # Shown in sidebar
website_descr = "Comprehensive documentation and guides"
+++
Color Themes
Lanyon supports multiple color schemes. You can customize colors by editing _css/hyde.css
:
/* Available theme colors */
.theme-base-08 { /* Red */}
.theme-base-09 { /* Orange */}
.theme-base-0a { /* Yellow */}
.theme-base-0b { /* Green */}
.theme-base-0c { /* Cyan */}
.theme-base-0d { /* Blue */}
.theme-base-0e { /* Magenta */}
.theme-base-0f { /* Brown */}
Navigation Menu
The sidebar navigation is automatically generated from your site structure, but you can customize it by editing the _layout
files.
Lanyon-Specific Franklin Commands
Lanyon works seamlessly with Franklin's built-in commands:
+++
title = "Your Page Title"
tags = ["example"]
hasmath = true
+++
<!-- Table of contents -->
\toc
<!-- Example: In-content variables (use @def) -->
<!-- @def author = "Your Name" -->
<!-- @def project_version = "1.0" -->
<!-- Example: Reference variables -->
<!-- This guide was created by {{fill author}} for version {{fill project_version}}. -->
<!-- Math expressions -->
$E = mc^2$
$$\int_0^1 x^2 dx = \frac{1}{3}$$
<!-- Code blocks with syntax highlighting -->
```julia
function hello_world()
println("Hello from Franklin + Lanyon!")
end
Mobile Responsiveness
Lanyon automatically adapts to different screen sizes:
Desktop: Full sidebar visible
Tablet: Sidebar toggleable with hamburger menu
Mobile: Collapsed sidebar with touch-friendly navigation
Performance Features
Lazy loading for improved page load times
Minified CSS/JS in production builds
Optimized images and assets
Fast navigation with minimal JavaScript
GitHub Pages Configuration
Step 6: DNS Setup (If Using Custom Domain)
Configure DNS records with your domain provider:
A Records (pointing to GitHub Pages IPs):
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
CNAME Record:
www
pointing to<your-github-username>.github.io
Step 7: GitHub Actions Workflow for Franklin/Lanyon with Subdirectory Preservation
Create .github/workflows/deploy.yml
with subdirectory documentation preservation:
name: Build and Deploy Franklin Site with Lanyon
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
workflow_dispatch: # Manual trigger for Franklin deployment
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false
# CRITICAL: Backup existing subdirectory documentation before Franklin deployment
- name: Backup subdirectory documentation
if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
run: |
# Create a temporary directory for backups
mkdir -p /tmp/subdocs_backup
# Clone the gh-pages branch to get existing subdirectory docs using GitHub token
git clone --depth=1 --branch=gh-pages https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/YourUsername/YourMainRepo.git /tmp/gh_pages_backup || echo "gh-pages branch doesn't exist yet"
# Copy subdirectory documentation if it exists
# IMPORTANT: Add each new subdirectory here when you create new subrepositories
if [ -d "/tmp/gh_pages_backup/linear_algebra" ]; then
cp -r /tmp/gh_pages_backup/linear_algebra /tmp/subdocs_backup/
echo "Backed up linear_algebra documentation"
fi
if [ -d "/tmp/gh_pages_backup/math_foundations" ]; then
cp -r /tmp/gh_pages_backup/math_foundations /tmp/subdocs_backup/
echo "Backed up math_foundations documentation"
fi
# ADD NEW SUBDIRECTORIES HERE - Template:
# if [ -d "/tmp/gh_pages_backup/new_project_name" ]; then
# cp -r /tmp/gh_pages_backup/new_project_name /tmp/subdocs_backup/
# echo "Backed up new_project_name documentation"
# fi
ls -la /tmp/subdocs_backup/
- name: Setup Julia
uses: julia-actions/setup-julia@v1
with:
version: '1.9'
- name: Cache Julia packages
uses: julia-actions/cache@v1
- name: Install Franklin and FranklinTemplates
run: |
julia -e 'using Pkg; Pkg.add("Franklin"); Pkg.add("FranklinTemplates")'
- name: Build Franklin site with Lanyon
run: julia -e 'using Franklin; optimize()'
# CRITICAL: Restore subdirectory documentation after Franklin build
- name: Restore subdirectory documentation
if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
run: |
# Restore backed up subdirectory documentation to __site folder
# IMPORTANT: Add each new subdirectory here when you create new subrepositories
if [ -d "/tmp/subdocs_backup/linear_algebra" ]; then
cp -r /tmp/subdocs_backup/linear_algebra __site/
echo "Restored linear_algebra documentation"
fi
if [ -d "/tmp/subdocs_backup/math_foundations" ]; then
cp -r /tmp/subdocs_backup/math_foundations __site/
echo "Restored math_foundations documentation"
fi
# ADD NEW SUBDIRECTORIES HERE - Template:
# if [ -d "/tmp/subdocs_backup/new_project_name" ]; then
# cp -r /tmp/subdocs_backup/new_project_name __site/
# echo "Restored new_project_name documentation"
# fi
echo "Contents of __site after restoration:"
ls -la __site/
- name: Deploy to GitHub Pages
if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./__site
cname: study.fourm.info # Replace with your domain
🚨 CRITICAL MAINTENANCE NOTE:
When adding new subrepositories that deploy documentation to subdirectories, you MUST update TWO sections in this workflow:
Backup Section: Add the new subdirectory to the backup logic
Restore Section: Add the new subdirectory to the restore logic
Template for new subdirectories:
# In backup section:
if [ -d "/tmp/gh_pages_backup/NEW_PROJECT_NAME" ]; then
cp -r /tmp/gh_pages_backup/NEW_PROJECT_NAME /tmp/subdocs_backup/
echo "Backed up NEW_PROJECT_NAME documentation"
fi
# In restore section:
if [ -d "/tmp/subdocs_backup/NEW_PROJECT_NAME" ]; then
cp -r /tmp/subdocs_backup/NEW_PROJECT_NAME __site/
echo "Restored NEW_PROJECT_NAME documentation"
fi
Why This Is Critical:
Franklin.jl overwrites the entire
gh-pages
branch when deployingWithout this backup/restore logic, subdirectory documentation gets deleted
Each subdirectory must be explicitly listed in both backup and restore sections
Missing this step will result in broken documentation links
Step 8: Build Utilities for Lanyon
Create utils.jl
for local development with Lanyon:
"""
optimize()
Build and optimize the Franklin site with Lanyon template for production deployment.
"""
function optimize()
using Franklin
# Clear any previous builds
if isdir("__site")
rm("__site", recursive=true)
end
# Set optimization flags
Franklin.optimize(;
minify=true,
prerender=true
)
println("✓ Lanyon site built and optimized in __site/ directory")
println("📱 Template: Lanyon (responsive)")
println("⚡ Minification: enabled")
end
"""
serve_local(port=8000)
Start local development server with Lanyon template.
"""
function serve_local(port=8000)
using Franklin
println("🚀 Starting Franklin development server with Lanyon...")
println("📱 Template: Lanyon")
println("🌐 URL: http://localhost:$port")
println("🔄 Auto-reload: enabled")
println("📱 Mobile-responsive sidebar available")
Franklin.serve(; clear=true, port=port)
end
"""
newpage(pagename::String; folder="")
Create a new page with proper Franklin/Lanyon structure.
"""
function newpage(pagename::String; folder="")
# Create the page path
if !isempty(folder)
pagepath = joinpath(folder, "$pagename.md")
mkpath(folder)
else
pagepath = "$pagename.md"
end
# Page template for Franklin/Lanyon
template = """
+++
title = "$(titlecase(replace(pagename, "-" => " ")))"
tags = ["page"]
hascode = false
+++
# $(titlecase(replace(pagename, "-" => " ")))
Welcome to the $(titlecase(replace(pagename, "-" => " "))) page.
\\toc
## Content
Add your content here.
## Navigation
- [Back to Home](/)
"""
write(pagepath, template)
println("✓ Created new page: $pagepath")
println("📝 Edit the page and run serve_local() to preview")
println("📱 Page will be mobile-responsive with Lanyon")
end
Create build.jl
for local builds with Lanyon:
#!/usr/bin/env julia
using Pkg
Pkg.activate(".")
# Ensure Franklin and FranklinTemplates are installed
try
using Franklin, FranklinTemplates
catch
Pkg.add("Franklin")
Pkg.add("FranklinTemplates")
using Franklin, FranklinTemplates
end
println("🏗️ Building Franklin site with Lanyon template...")
# Include utilities if available
if isfile("utils.jl")
include("utils.jl")
optimize()
else
Franklin.optimize(; minify=true, prerender=true)
end
println("✅ Build complete! Lanyon site available in __site/ directory")
println("� Template: Lanyon (mobile-responsive)")
println("�🚀 To deploy: push to main branch")
println("🔍 To preview locally: julia -e 'include(\"utils.jl\"); serve_local()'")
Step 9: Repository Settings
Enable GitHub Pages:
Navigate to repository → Settings → Pages
Set source to "Deploy from a branch"
Select branch:
gh-pages
Set folder to
/ (root)
Add custom domain:
study.fourm.info
Enable HTTPS enforcement
Configure GitHub Actions:
Go to Settings → Actions → General
Under "Workflow permissions", select "Read and write permissions"
Check "Allow GitHub Actions to create and approve pull requests"
Subrepository Integration
Step 10: Creating DrWatson.jl Projects
For new subrepositories using DrWatson.jl, follow these steps:
Initialize DrWatson Project
using DrWatson
# Create a new project with documentation
DrWatson.initialize_project("MyProject";
authors="Your Name",
julia=true,
git=true,
readme=true,
add_docs=true,
github_name="Github User or Organization",
compat=true)
Create Project Module
In the src
folder, create a file named MyProject.jl
:
module MyProject
# Reexport commonly used packages
using Reexport
@reexport using Plots, Latexify, LaTeXStrings, Dates
# Set plotting backend
gr()
# Export functions from the module
export my_function1, my_function2
export my_function3, my_function4
# Re-export macros (example)
eval(:(export @variables))
# Include source code files
include("my_file.jl")
end # module
Step 11: Generate Documentation Deployment Keys
For each subrepository, generate deployment keys using DocumenterTools:
using DocumenterTools
DocumenterTools.genkeys()
Sample output:
┌ Info: Add the key below as a new 'Deploy key' on GitHub with read and write access.
└ The 'Title' field can be left empty as GitHub can infer it from the key comment.
ssh-rsa AAAAB3NzaC2yc2EAAAaDAQABAAABAQDrNsUZYBWJtXYUk21wxZbX3KxcH8EqzR3ZdTna0Wgk...
[ Info: Add a secure 'Repository secret' named 'DOCUMENTER_KEY' with value:
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBNnpiRkdXQVZpYlIy...
Setup Instructions:
Add Deploy Key:
Go to your repository → Settings → Deploy keys
Add new key with the SSH key from the output
Check "Allow write access"
Add Repository Secret:
Go to your repository → Settings → Secrets and variables → Actions
Add new secret named
DOCUMENTER_KEY
Use the private key value from the output
Step 12: Custom Domain Verification (If Using Custom Domain)
If you're using a custom domain across multiple repositories:
Verify Domain Ownership:
This prevents other users from taking over your domain
Configure Domain in Main Repository Only:
Set custom domain only in the main repository (this one)
All subrepositories deploy to subdirectories of the main domain
Step 13: Subrepository CI Configuration with Manual Triggers
Important: Do NOT enable GitHub Pages on subrepositories. They deploy to the main repository.
For each subrepository, update .github/workflows/CI.yml
with manual trigger support:
name: CI
on:
push:
branches:
- main
tags: ['*']
pull_request:
workflow_dispatch: # Manual trigger for documentation deployment
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1'
os:
- ubuntu-latest
arch:
- x64
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/cache@v1
- uses: julia-actions/julia-buildpkg@v1
- name: Run tests
run: >
julia --project=. --color=yes test/runtests.jl
deploy-docs:
name: Deploy Documentation
runs-on: ubuntu-latest
needs: test
# Only deploy on push to main (not on PRs) OR manual trigger
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
with:
version: '1'
arch: x64
- uses: julia-actions/cache@v1
- uses: julia-actions/julia-buildpkg@v1
- name: Generate documentation and deploy
run: >
julia --project=. --color=yes docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
🚨 Manual Trigger Usage:
When subdirectory documentation gets overwritten by Franklin deployment:
Go to the affected subrepository on GitHub
Navigate to Actions → CI → "Run workflow"
Click "Run workflow" to manually redeploy the subdirectory documentation
This will restore the missing subdirectory documentation
Step 14: Subrepository Documentation Configuration
Update each subrepository's docs/make.jl
:
CI = get(ENV, "CI", nothing) == "true" || get(ENV, "GITHUB_TOKEN", nothing) !== nothing
using DrWatson, Documenter
using YourProjectName # Replace with actual project name
@info "Building Documentation"
makedocs(;
modules=[YourProjectName],
sitename = "YourProjectName",
pages = ["index.md"],
format = Documenter.HTML(
prettyurls = CI,
edit_link = "main",
# Match Franklin's styling
assets = [
"assets/custom.css",
],
collapselevel = 1,
)
)
@info "Deploying Documentation"
if CI
deploydocs(
# Deploy to the main repository that hosts the custom domain
repo = "github.com/YourUsername/YourMainRepo.git", # Update this
target = "build",
push_preview = true,
devbranch = "main",
# Deploy to a specific subdirectory for this repository
dirname = "your_project_name", # Update this
)
end
@info "Finished with Documentation"
Step 15: Cross-Repository Permissions
Set up deployment permissions:
Generate Personal Access Token (PAT):
Go to GitHub → Settings → Developer settings → Personal access tokens
Create token with
repo
permissionsCopy the token
Add to Subrepository Secrets:
In each subrepository: Settings → Secrets and variables → Actions
Add new secret named
DOCUMENTER_KEY
Paste the PAT as the value
Verify Permissions:
Ensure the PAT has write access to the main repository
Test deployment with a small change
Style Consistency
Step 13: Customizing Lanyon Styling
Lanyon comes with beautiful default styling, but you can customize it. Create _css/custom.css
:
/* Lanyon customizations */
/* Override Lanyon's sidebar colors */
.sidebar {
background-color: #202020;
}
.sidebar-about h1 {
color: white;
font-family: "PT Sans", Helvetica, Arial, sans-serif;
}
.sidebar-about .lead {
color: rgba(255,255,255,.5);
}
/* Customize content area */
.content {
font-family: "PT Serif", Georgia, "Times New Roman", serif;
}
/* Custom content blocks for project links */
.posts .post {
margin-bottom: 2rem;
padding: 1.5rem;
border-left: 3px solid #268bd2;
background: #f9f9f9;
}
.posts .post:hover {
background: #f5f5f5;
transition: background 0.2s ease;
}
/* Enhanced code blocks */
.highlight {
background: #f8f8f8;
border-radius: 4px;
border: 1px solid #e1e1e8;
margin: 1rem 0;
}
/* Custom note and warning blocks */
.note {
background: #e7f3ff;
border-left: 4px solid #2196F3;
padding: 1rem;
margin: 1rem 0;
}
.warning {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 1rem;
margin: 1rem 0;
}
/* Mobile responsiveness enhancements */
@media (max-width: 768px) {
.posts .post {
margin: 0.5rem 0;
padding: 1rem;
}
.content {
padding: 1rem;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.posts .post {
background: #2b2b2b;
color: #e0e0e0;
}
.highlight {
background: #1e1e1e;
border-color: #404040;
}
}
Step 14: Documenter Style Matching for Lanyon
For each subrepository, create docs/src/assets/custom.css
to match your Franklin/Lanyon site:
/* Match Lanyon color scheme */
:root {
--documenter-primary: #268bd2; /* Lanyon's default blue */
--documenter-secondary: #2b2b2b;
--documenter-sidebar-bg: #202020;
}
.docs-main {
font-family: "PT Serif", Georgia, "Times New Roman", serif; /* Match Lanyon content font */
}
/* Match Lanyon's header styling */
.docs-main h1, .docs-main h2, .docs-main h3 {
color: var(--documenter-primary);
font-family: "PT Sans", Helvetica, Arial, sans-serif; /* Match Lanyon headers */
}
/* Sidebar styling to match Lanyon */
.docs-sidebar {
background-color: var(--documenter-sidebar-bg);
}
.docs-sidebar .docs-logo {
text-align: center;
padding: 1rem;
border-bottom: 1px solid #424242;
}
.docs-sidebar .docs-logo::after {
content: "Part of Study Guides";
display: block;
font-size: 0.8em;
color: rgba(255,255,255,0.5); /* Match Lanyon sidebar text color */
margin-top: 0.5rem;
}
/* Code block styling to match Lanyon */
.hljs {
background: #f8f8f8 !important;
border-radius: 3px;
border: 1px solid #e1e1e8;
}
/* Responsive design matching Lanyon */
@media (max-width: 768px) {
.docs-main {
padding: 1rem;
}
}
/* Dark mode adjustments */
@media (prefers-color-scheme: dark) {
.hljs {
background: #1e1e1e !important;
border-color: #404040;
}
}
Step 15: Layout Templates
Create _layout/subproject.html
for consistent subproject styling:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{fill title}} {{isdef title}} | {{end}} {{fill website_title}}
{{insert head.html}}
</head>
<body>
{{insert topnav.html}}
<div class="container">
<!-- Breadcrumb navigation -->
<nav class="breadcrumb">
<a href="/">Home</a> ›
<a href="/{{fill project_name}}/">{{fill project_title}}</a>
</nav>
<div class="content">
{{fill fd_content}}
</div>
</div>
{{insert foot.html}}
</body>
</html>
Deployment and Testing
Step 16: Initial Deployment
Test Local Build:
julia build.jl
Preview Locally:
julia -e 'using Franklin; serve()'
Deploy to GitHub Pages:
git add .
git commit -m "Initial Franklin setup"
git push origin main
Step 17: Verification Checklist
Main Site Checks:
[ ] Franklin site builds without errors
[ ] Custom domain resolves correctly
[ ] All pages load properly
[ ] Navigation works as expected
[ ] Styling is consistent
Subrepository Checks:
[ ] Documentation builds in subrepositories
[ ] Subdirectories deploy to correct paths
[ ] Cross-repository permissions work
[ ] Styling matches main site
[ ] Links between sites work
Step 18: Performance Optimization
Add to config.md
:
+++
# ...existing config...
# Performance optimizations
generate_rss = true
rss_file = "feed.xml"
rss_full_content = false
# SEO optimization
robots = true
sitemap = true
+++
Security and Maintenance Considerations
Repository Secrets Management
Best Practices for DOCUMENTER_KEY:
Regular Key Rotation:
Regenerate deployment keys every 6-12 months
Update secrets in all repositories using the key
Document key rotation procedures
Minimal Permissions:
Use repository-specific deploy keys when possible
Avoid using personal access tokens with broad scopes
Regularly audit repository access permissions
Secret Storage:
Never commit secrets to version control
Use GitHub's built-in secrets management
Consider using organization-level secrets for shared keys
Domain Security
DNS Security:
Enable DNSSEC on your domain
Use CAA records to restrict certificate authorities
Monitor DNS changes for unauthorized modifications
GitHub Pages Security:
Always enforce HTTPS
Regularly verify domain ownership
Monitor for unauthorized repository access
Dependency Management
Julia Package Management:
Pin critical package versions in Manifest.toml
Regularly update dependencies for security patches
Use Pkg.update() carefully in production workflows
GitHub Actions Security:
Pin action versions to specific commits
Regularly update to latest stable versions
Review third-party actions for security issues
Performance Optimization
Franklin.jl Optimization
Build Performance:
# In config.md, optimize for faster builds
+++
# ...other config...
# Performance settings
hasmath = false # Disable if no math rendering needed
hascode = true # Enable code highlighting
minify = true # Minify output HTML/CSS
generate_rss = false # Disable if RSS not needed
+++
Content Optimization:
Optimize images (use WebP format when possible)
Minimize external dependencies
Use lazy loading for large content sections
Documenter.jl Optimization
Build Speed:
# In docs/make.jl
makedocs(;
modules=[YourModule],
sitename="YourSite",
# Optimization settings
checkdocs=:exports, # Only check exported functions
linkcheck=false, # Disable external link checking for speed
clean=true, # Clean build directory
)
Documentation Size:
Use
@meta
blocks to control page generationMinimize large code examples in docstrings
Use external links for extensive examples
Monitoring and Analytics
Site Performance Monitoring
GitHub Pages Analytics:
Monitor site loading times
Track documentation usage patterns
Set up alerts for build failures
Content Validation:
# Add to local testing workflow
using Franklin
# Validate all internal links
function validate_links()
# Your link validation code
println("Validating internal links...")
end
# Check for broken references
function check_references()
# Your reference checking code
println("Checking documentation references...")
end
Automated Quality Checks
Pre-commit Hooks:
# .git/hooks/pre-commit
#!/bin/bash
# Run Julia code formatting
julia --project=. -e 'using JuliaFormatter; format(".")'
# Validate documentation builds locally
julia --project=docs docs/make.jl
# Check for common issues
echo "Running quality checks..."
Continuous Integration Enhancements:
# Add to .github/workflows/CI.yml
- name: Check documentation links
run: |
julia --project=docs -e '
using Documenter
# Add link checking code here
'
- name: Validate Franklin build
run: |
julia -e 'using Franklin; verify()'
Conclusion and Final Checklist
This comprehensive setup provides a robust, scalable documentation ecosystem that combines the flexibility of Franklin.jl for main content with the power of Documenter.jl for code documentation. The centralized GitHub Pages deployment ensures consistent hosting while maintaining the ability to manage content across multiple repositories.
Pre-Deployment Checklist
Main Repository (Franklin.jl with Lanyon) Setup
[ ] Julia packages installed (
Franklin
,FranklinTemplates
)[ ]
Project.toml
configured with correct dependencies[ ] Lanyon template initialized with
newsite(".", template="lanyon")
[ ]
config.md
created with Franklin/Lanyon configuration[ ] Main
index.md
created with Lanyon-compatible navigation[ ] Custom CSS files created (
_css/custom.css
) for Lanyon customization[ ] GitHub Actions workflow configured for Franklin/Lanyon (
.github/workflows/deploy.yml
)[ ] Repository Pages settings configured
[ ] Custom domain DNS records configured (if applicable)
[ ] Local build tested successfully with
julia -e 'using Franklin; serve()'
[ ] Responsive sidebar navigation works correctly
[ ] Mobile responsiveness verified
Subrepository (DrWatson.jl + Documenter.jl) Setup
[ ] DrWatson project initialized
[ ] Main module file created (
src/ProjectName.jl
)[ ] Documentation dependencies added (
DocumenterTools
,Documenter
)[ ] Deploy keys generated with
DocumenterTools.genkeys()
[ ] GitHub deploy key added to repository settings
[ ]
DOCUMENTER_KEY
secret added to repository[ ]
docs/make.jl
configured correctly[ ]
.github/workflows/CI.yml
updated[ ] Custom CSS created to match Franklin/Lanyon theme
[ ] Local documentation build tested
[ ] Cross-repository deployment tested
Integration Verification
[ ] Main Franklin site loads at correct URL
[ ] Subrepository documentation deploys to correct subdirectories
[ ] Styling is consistent across all sites
[ ] Navigation between main site and subprojects works
[ ] All internal links function correctly
[ ] Mobile responsiveness verified
[ ] Search functionality works (if enabled)
[ ] RSS feeds generate correctly (if enabled)
Key Benefits of This Architecture
Unified Experience: Single domain with consistent styling across all documentation
Scalable: Easy to add new subrepositories without conflicts
Automated: GitHub Actions handle all building and deployment
Cost-Effective: Free hosting with GitHub Pages
Maintainable: Clear separation of concerns between main site and subprojects
Flexible: Franklin for guides/tutorials, Documenter for API documentation
Secure: Proper key management and access controls
Maintenance Schedule
Weekly Tasks
Monitor GitHub Actions for failed builds
Check for broken links across all sites
Review site performance metrics
Monthly Tasks
Update Julia package versions
Review and update documentation content
Check security settings and permissions
Backup important configuration files
Quarterly Tasks
Update Franklin.jl and Documenter.jl to latest versions
Review site architecture for improvements
Update styling and themes as needed
Security audit of keys and permissions
Support Resources
For additional help and documentation:
Franklin.jl: Official Documentation
Documenter.jl: Official Documentation
DrWatson.jl: Official Documentation
GitHub Pages: Official Documentation
GitHub Actions: Official Documentation
Common Gotchas and Important Notes
Domain Configuration: Only set custom domain in ONE repository (the main one)
Deployment Keys: Each subrepository needs its own
DOCUMENTER_KEY
secretModule Names: Ensure module names in
docs/make.jl
match actual module namesRepository URLs: Double-check repository URLs in deployment configuration
Branch Names: Ensure branch names match between CI configuration and actual branches
Permissions: Verify repository permissions allow cross-repository deployment
Case Sensitivity: Be careful with case sensitivity in file paths and URLs
Build Order: Main Franklin site and subrepositories build independently
🚨 CRITICAL: Subdirectory Preservation - See troubleshooting section below
🚨 Critical Troubleshooting: Subdirectory Documentation Preservation
Problem: Subdirectory Documentation Gets Deleted
Symptoms:
Main Franklin site deploys successfully
Subdirectory documentation (e.g.,
/linear_algebra/
,/math_foundations/
) returns 404 errorsSubdirectories were working before Franklin redeployment
Cause: Franklin.jl overwrites the entire gh-pages
branch, deleting subdirectory documentation deployed by subrepositories.
Solutions:
Verify Backup/Restore Logic is Present: Check that your
.github/workflows/deploy.yml
includes both backup and restore steps (see Step 7 above).Add Missing Subdirectories to Workflow: If you've added new subrepositories, you MUST update the Franklin GitHub Actions workflow:
# Add to backup section:
if [ -d "/tmp/gh_pages_backup/NEW_SUBDIRECTORY" ]; then
cp -r /tmp/gh_pages_backup/NEW_SUBDIRECTORY /tmp/subdocs_backup/
echo "Backed up NEW_SUBDIRECTORY documentation"
fi
# Add to restore section:
if [ -d "/tmp/subdocs_backup/NEW_SUBDIRECTORY" ]; then
cp -r /tmp/subdocs_backup/NEW_SUBDIRECTORY __site/
echo "Restored NEW_SUBDIRECTORY documentation"
fi
Emergency Recovery: If subdirectories are missing:
a. Manually trigger subrepository deployments:
Go to each subrepository → Actions → CI → "Run workflow"
This will redeploy the missing subdirectory documentation
b. Manually trigger Franklin deployment:
Go to main repository → Actions → "Build and Deploy" → "Run workflow"
Verify backup/restore logic works in the logs
Verification Checklist:
[ ] Franklin workflow includes backup and restore steps
[ ] All current subdirectories are listed in both backup and restore sections
[ ] GitHub token authentication is working (
x-access-token:${{ secrets.GITHUB_TOKEN }}
)[ ] Manual triggers are available on all repositories (
workflow_dispatch
)
GitHub Actions Logs to Check
Backup Step - Should show:
Backed up linear_algebra documentation
Backed up math_foundations documentation
total 24
drwxr-xr-x 4 runner docker 4096 Jul 9 12:03 .
...
drwxr-xr-x 3 runner docker 4096 Jul 9 12:03 linear_algebra
drwxr-xr-x 3 runner docker 4096 Jul 9 12:03 math_foundations
Restore Step - Should show:
Restored linear_algebra documentation
Restored math_foundations documentation
Contents of __site after restoration:
...
drwxr-xr-x 3 runner docker 4096 Jul 9 12:05 linear_algebra
drwxr-xr-x 3 runner docker 4096 Jul 9 12:05 math_foundations
If backup fails:
Check GitHub token permissions
Verify repository URL in clone command
Ensure gh-pages branch exists
If restore fails:
Check that backup succeeded first
Verify __site directory exists after Franklin build
Check file permissions in GitHub Actions runner
Maintenance Checklist for New Subdirectories
When adding a new subrepository that deploys documentation:
Update Franklin GitHub Actions Workflow:
[ ] Add new subdirectory to backup section
[ ] Add new subdirectory to restore section
[ ] Test with manual trigger
Configure Subrepository:
[ ] Add
workflow_dispatch
trigger to subrepository CI[ ] Set up proper deployment in
docs/make.jl
[ ] Test manual deployment trigger
Verify Integration:
[ ] Deploy subrepository documentation
[ ] Deploy Franklin site (should preserve subdirectory)
[ ] Confirm all links work correctly
This architecture has been tested and provides a solid foundation for comprehensive documentation sites that scale from individual projects to large multi-repository ecosystems.
Website built with Lanyon, the Github Pages