FourM Math & Tech Study Hub

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 of template="basic"

  • Requires FranklinTemplates package in addition to Franklin

  • Uses +++ syntax for page headers (YAML front matter) and @def for in-content variables

  • Built-in responsive sidebar navigation

  • Mobile-first design approach

  • Pre-configured CSS and layout files

Table of Contents

  1. Prerequisites and Planning

  2. Franklin.jl Main Site Setup

  3. Lanyon Template Features

  4. GitHub Pages Configuration

  5. Subrepository Integration

  6. Style Consistency

  7. Deployment and Testing

  8. Security and Maintenance Considerations

  9. Conclusion and Final Checklist

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 committed

  • Commit Project.toml: Essential for package dependencies

  • Keep 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:

  1. Update your .gitignore to ignore Manifest.toml:

Manifest.toml
  1. Pin critical versions in Project.toml:

[deps]
   Franklin = "0.10"  # Pin major.minor for stability
   FranklinTemplates = "0.9"
  1. 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()"
  1. 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 content

  • Variable 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 content

  • Both 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

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 */}

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
This is a custom note block
This is a warning block

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:

  1. Backup Section: Add the new subdirectory to the backup logic

  2. 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 deploying

  • Without 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

  1. 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

  2. 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:

  1. Add Deploy Key:

    • Go to your repository → Settings → Deploy keys

    • Add new key with the SSH key from the output

    • Check "Allow write access"

  2. 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:

  1. Verify Domain Ownership:

  2. 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:

  1. Go to the affected subrepository on GitHub

  2. Navigate to Actions → CI → "Run workflow"

  3. Click "Run workflow" to manually redeploy the subdirectory documentation

  4. 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:

  1. Generate Personal Access Token (PAT):

    • Go to GitHub → Settings → Developer settings → Personal access tokens

    • Create token with repo permissions

    • Copy the token

  2. Add to Subrepository Secrets:

    • In each subrepository: Settings → Secrets and variables → Actions

    • Add new secret named DOCUMENTER_KEY

    • Paste the PAT as the value

  3. 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

  1. Test Local Build:

julia build.jl
  1. Preview Locally:

julia -e 'using Franklin; serve()'
  1. 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:

  1. Regular Key Rotation:

    • Regenerate deployment keys every 6-12 months

    • Update secrets in all repositories using the key

    • Document key rotation procedures

  2. Minimal Permissions:

    • Use repository-specific deploy keys when possible

    • Avoid using personal access tokens with broad scopes

    • Regularly audit repository access permissions

  3. 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

  1. DNS Security:

    • Enable DNSSEC on your domain

    • Use CAA records to restrict certificate authorities

    • Monitor DNS changes for unauthorized modifications

  2. GitHub Pages Security:

    • Always enforce HTTPS

    • Regularly verify domain ownership

    • Monitor for unauthorized repository access

Dependency Management

  1. Julia Package Management:

    • Pin critical package versions in Manifest.toml

    • Regularly update dependencies for security patches

    • Use Pkg.update() carefully in production workflows

  2. 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

  1. 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
   +++
  1. Content Optimization:

    • Optimize images (use WebP format when possible)

    • Minimize external dependencies

    • Use lazy loading for large content sections

Documenter.jl Optimization

  1. 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
   )
  1. Documentation Size:

    • Use @meta blocks to control page generation

    • Minimize large code examples in docstrings

    • Use external links for extensive examples

Monitoring and Analytics

Site Performance Monitoring

  1. GitHub Pages Analytics:

    • Monitor site loading times

    • Track documentation usage patterns

    • Set up alerts for build failures

  2. 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

  1. 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..."
  1. 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:

Common Gotchas and Important Notes

  1. Domain Configuration: Only set custom domain in ONE repository (the main one)

  2. Deployment Keys: Each subrepository needs its own DOCUMENTER_KEY secret

  3. Module Names: Ensure module names in docs/make.jl match actual module names

  4. Repository URLs: Double-check repository URLs in deployment configuration

  5. Branch Names: Ensure branch names match between CI configuration and actual branches

  6. Permissions: Verify repository permissions allow cross-repository deployment

  7. Case Sensitivity: Be careful with case sensitivity in file paths and URLs

  8. Build Order: Main Franklin site and subrepositories build independently

  9. 🚨 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 errors

  • Subdirectories were working before Franklin redeployment

Cause: Franklin.jl overwrites the entire gh-pages branch, deleting subdirectory documentation deployed by subrepositories.

Solutions:

  1. Verify Backup/Restore Logic is Present: Check that your .github/workflows/deploy.yml includes both backup and restore steps (see Step 7 above).

  2. 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
  1. 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

  2. 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:

  1. Update Franklin GitHub Actions Workflow:

    • [ ] Add new subdirectory to backup section

    • [ ] Add new subdirectory to restore section

    • [ ] Test with manual trigger

  2. Configure Subrepository:

    • [ ] Add workflow_dispatch trigger to subrepository CI

    • [ ] Set up proper deployment in docs/make.jl

    • [ ] Test manual deployment trigger

  3. 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.

CC BY-NC-SA 4.00 Aron Trauring. Last modified: July 23, 2025.
Website built with Lanyon, the Github Pages