Content Management (SEO Pages)
Every SaaS needs a blog system so you can attract customers through search or use content as marketing - this page is marketing for ShipClojure :P.
ShipClojure Datom uses powerpack to generate the static pages (these docs use the same principle). Powerpack works by taking markdown files and converting them html pages that when deployed, our server serves. To index all of the pages, powerpack uses an in-memory datomic DB. Why? Because datomic is great at doing graph based knowledge bases. The blog system supports:
- Author pages
- Tags
- Any other custom pages
A tutorial for powerpack directly lives here. If you want to go in depth with powerpack content pages, it's a good idea to follow that tutorial.
Content
Your content posts live in the content directory. There you'll find 2-3 dummy blog posts to get you started. These can be blog posts, but also articles, about page, and everything else you deem worthy.
Basic format
Powerpack uses mapdown - a very cool tool that let's you kind of represent a map inside markdown. You get
{:page/uri "/about"
:page/title "Let me tell you about me"
:page/body "#Intro\nI was born on a stormy winter night..."}
from
:page/uri /about
:page/title Let me tell you about me
:page/body
# Intro
I was born on a stormy night...
Which is super cool. I view it like writing markdown articles but with clojure metadata attached to them. The docs you are reading here are written in the same way. Here's an excerp, we are going a bit meta here:
:page/title Content Management (SEO Pages)
:shipclojure/stack :shipclojure.stack/datom
:page/uri /docs/datom/content-management-(seo-pages)/
:page/category :page.category/frontend
:page/body
Every SaaS needs a blog system so you can attract customers through search or
use content as marketing - this page is marketing for ShipClojure :P.
ShipClojure Datom uses [powerpack](https://github.com/cjohansen/powerpack) to
generate the static pages (these docs use the same principle). Powerpack works
by taking markdown files and converting them html pages that when deployed, our
server serves. To index all of the pages, powerpack uses an in-memory datomic
DB. Why? Because datomic is great at doing graph based knowledge bases. The blog
system supports:
...
New blog post
Ok, let's say you want to create a new blog post:
If not already created, create a
content/authors/yourname.ednfile as an author page - we can instruct powerpack to read both/docs/datom/frontend/andednfiles are input for content. Here's mine:content/authors/ovistoica.edn {:person/id :ovistoica :person/given-name "John" :person/family-name "Stoica" :person/full-name "Ovi Stoica" :person/email "ovi@shipclojure.com" :person/bio "Clojure developer passionate about building fast, data-driven SaaS products." :person/photo "/assets/images/avatars/ovistoica.png"}Now create a new file in
/docs/datom/frontend/content/blog-posts/my-cool-blog//docs/datom/frontend/content/blog-posts/my-cool-blog/ :page/title "Welcome to the ShipClojure Blog" :page/uri "/blog/welcome-to-shipclojure-blog/" :blog-post/description "Introducing the ShipClojure blog - insights on building SaaS products with Clojure" :blog-post/author {:person/id :ovistoica} :blog-post/published-at #inst "2024-01-15T10:00:00.000-00:00" :blog-post/tags [:clojure :saas :shipclojure :announcement] :blog-post/featured? true :open-graph/title "Welcome to ShipClojure Blog" :open-graph/description "Learn how to build and ship SaaS products fast with Clojure" :page/body Welcome to the official ShipClojure blog! This is where we'll share insights, tutorials, and best practices for building SaaS products with Clojure. ## What You'll Find Here Our blog will cover:As you can see you can add a lot of data to these. Traditionally you would use frontmatter to add meta details to your markdown files, but mapdown has more details that we can take advantage of as clojure programmers.
Exporting
To export your blogs to html pages, you run bb build:content which will run the export funciton from powerpack. There are differences between content handing in development vs production.
Development
In development we have a middleware that dynamically serves the content page (see wrap-serve-powerpack-pages) so that you can visualize how your blog would look in realtime. You can also call powerpack-start from dev/saas/dev.clj to start the powerpack server which has live-reloading capabilities.
Production
For production builds, we export the blogs and content pages to static html files that are just served directly from our server. This boosts delivery time and the SEO score for the webpages.
You can see this in the content section of the Dockerfile
# Blog & Content static file export
COPY env/dev/ env/dev/
COPY content/ content/
COPY resources/content-schema.edn resources/content-schema.edn
RUN bb build:content
# This script makes sure only generated HTML files get copied so we have optimus
# handle asset optmization
RUN bb copy:content
Blocks
Blocks are a way to structure your content pages as a collection of blocks. This is helpful when you want to create custom blocks that do specific effects. Mapdown supports reading a list of entries if you separate them by -------------------------------------------------------------------------------. More details here.
You can find an example blog that uses blocks in this blog example: /docs/datom/frontend/content/blog-posts/using-react-query-from-clojurescript/.
:block/title Blocks
:block/level 2
:block/id blocks
:block/markdown
Welcome to the official ShipClojure blog! This is where we'll share
insights, tutorials, and best practices for building SaaS products with Clojure.
## What You'll Find Here
Our blog will cover:
Blocks are handled in src/cljc/saas/ui/content.cljc in render-block. There you can define all kinds of blocks, here's an image block example:
:block/image /assets/images/logo.png
:block/image-alt "ShipClojure Logo"
:block/caption This is a logo I did myself #proud
It renders like this:
This is a logo I did myself #proud
You can extend this functionality to do way more. If you find blocks to be overkill, than just simply don't use then and write markdown after a :page/body entry and you should be fine. But blocks are there in case you need them.
Files involved in content management
If you want to dig deeper about where and how this happens, have a look at these files:
env/dev/clj/saas/content/core.clj- main powerpack initialization during developmentenv/dev/clj/saas/content/ingest.clj- rules for how to ingest content. Example: If the/docs/datom/frontend/file is undercontent/blog-posts/it is a blog post and therefore has:page/kind:page.kind/blog-postby default.env/dev/clj/saas/content/pages.clj- what hiccup generating function to use for each page kind.env/dev/clj/saas/content/handlers.clj- this namespace creates a handler used in wrap-serve-powerpack-pages to dynamically serve the content pages when the URI is accessed.