Updated at 2017-06-10 10:11

Lektor is static site generator made with Python. Lektor doesn't make assumptions what you website will contain; compared to e.g. Jekyll which is meant for blogs.

Install Lektor and create base project:

curl -sf | sh
lektor quickstart
lektor server

Each directory inside contents/ with a file is called a record. Attachments are also called records as they have their own model etc. but they are less frequently called records.

  • Record ID: Name of the folder containing the
  • Record Slug: Defaults to ID, can be customized with _slug

Record model definition:

  1. Checks if Record has _model field defined.
  2. Checks [children] model: is in parent model definition.
  3. Checks if Record ID as model name is valid model.
  4. Checks if plain page is a valid model.
  5. Gives up and uses none


  • Template: Defaults model_name.html, can be customized with _template
  • Alternative: Alternative partial or full version of a content. Usually used for translations.
  • Attachment: Everything next to files, publicly accessible.
  • Data Bag: Global potentially nested key-value objects.
  • Flow Block: Reusable components with their own model and template.
  • Source Object: Record ( and attachments in contents/) or Asset (in assets/).

Templates are stored in /templates, use Jinja2 and you have the following Python context:

this    = current Record being rendered
alt     = current Alternative (e.g language) of the page
site    = Pad helper class for site-wide queries
config  = Lektor project configuration
{% set root = site.get('/') %}
<title>{{ this.title }} | {{ root.title }}</title>

  {% for project in site.query('/projects') %}
    <li>{{ }}: {{ project.year }}</li>
  {% endfor %}

URLs are defined with |url and |asseturl helpers. asseturl helper adds hash query string to refresh visitor caches on changes.

<a href="{{ '/about'|url }}">About</a>
<a href="{{ '.'|url(alt='ru') }}">Русский</a>
<a href="{{ page|url }}">{{ page.title }}</a>
<link rel="stylesheet" href="{{ '/static/styles.css'|asseturl }}">

Rsync Deployment

name = Production
enabled = yes
default = yes
target = rsync://server/path/to/folder
# See where the default lektor build directory is for this project.
lektor project-info --output-path

# Build and deploy it.
lektor build && lektor deploy production

# Build to non-default local path.
lektor build --output-path ./build

S3 Deployment


name = Example
url =
url_style = absolute

lektor-s3 = 0.4.0

name = S3
enabled = yes
target = s3://
cloudfront = 3AB_EXAMPLE_CD7


CacheControl = public,max-age=3600

[static files]
match = \.(css|js|woff|woff2)$
CacheControl = public,max-age=31536000

extensions = jpg,jpeg,png,mp4,svg,gif
CacheControl = public,max-age=2592000

extensions = woff2
ContentType = application/font-woff2

extensions = html,txt
ContentLanguage = en
lektor build && lektor deploy s3
# Will build the project, upload it to S3 and invalidate CloudFront cache.

# If you change the header settings in s3.init, empty the S3 bucket first.