org-publish maps a tree of source Org files to a tree of output HTML, then leaves deployment as a separate, explicit step.
Publishing maps source projects to output projects. Conceptually:
notes/*.org → org-publish → public/*.html
assets/* → org-publish → public/assets/*You declare each project in org-publish-project-alist, then group them under a component project:
(setq org-publish-project-alist
'(("site-notes"
:base-directory "~/site/notes/"
:base-extension "org"
:publishing-directory "~/site/public/"
:recursive t
:publishing-function org-html-publish-to-html
:with-author nil
:with-creator nil
:html-head "<link rel=\"stylesheet\" href=\"/assets/site.css\">")
("site-assets"
:base-directory "~/site/assets/"
:base-extension "css\\|png\\|jpg\\|svg"
:publishing-directory "~/site/public/assets/"
:recursive t
:publishing-function org-publish-attachment)
("site" :components ("site-notes" "site-assets"))))Run M-x org-publish-project and choose site. Org records timestamps for incremental publishing; pass a prefix argument or run org-publish-all when a complete rebuild is necessary. Deployment is a separate, explicit step: copying to a server, pushing a hosting branch, or uploading an artifact.
When it fits, and when it does not
Org publishing is excellent when Org is already the authoring source and the site is mostly documents. Dedicated static-site generators usually provide richer themes, asset pipelines, taxonomies, preview servers, and contributor ecosystems. Org can also feed one: export Markdown or HTML as an intermediate artifact, but understand the additional transformation you are taking on.
For a large web book, exporting one page per chapter can improve loading and URLs. org-publish maps a source tree recursively, but navigation among pages still needs templates, preamble and postamble, a sitemap, or a separate site layer. One long page offers easy search but slower load. Choose based on audience and maintenance.
Incremental correctness and safety
Incremental timestamps save time but do not know every external dependency. Changing a shared CSS file is handled through the asset project, but changing an included file, a macro, or exporter code may require a forced rebuild. Production publishing should have a clean-build option and compare output before deployment.
Never publish directly into the only copy of hand-edited files. Treat public/ as generated output that can be deleted and rebuilt. Deployment credentials should be supplied by the deployment environment, not stored in org-publish-project-alist or the source repository.
Summary. A project alist declares base directories, extensions, and publishing functions. Incremental timestamps save time but miss external dependencies, so keep a clean-build option and treat public/ as generated output.
Exercises
- Basic: Publish one Org page and one CSS file locally with `M-x org-publish-project`.
- Practical: Add two linked pages, verify relative links resolve inside `public/`, and confirm changing the shared CSS republishes through the asset project.
- Advanced: Perform a clean batch rebuild that detects stale generated figures, compare output before deploying, and deploy only the validated result with credentials supplied by the environment.