DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,673 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for How I moved my blog to a static site using Dojo
Rene Rubalcava
Rene Rubalcava

Posted on • Originally published at learn-dojo.com

How I moved my blog to a static site using Dojo

I've shown how you can use dojo blocks with build-time-rendering to create static web pages. This is pretty powerful, because it means you could build an entire website without having to back it with API and database calls. Of course, this isn't ideal for web applications that require dynamic data or handle authorization, but it is ideal for situations where the content is fairly static, like documentation or my blog! This is very much what something like gatsby is for. All the pieces to build a static site generator are there in dojo, you just need to put them together.

Parsing

I was heavily inspired by what the dojo team is doing with dojo/site to statically build the pages for the next dojo documentation. I borrowed heavily from their blocks to parse markdown to virtual dom nodes as I found I was recreating the wheel.

My main goal for my static site generator was to parse markdown to pages, specifically blog pages. The core tools in this process are unified and remark. If you have built sites with gatsby, you may be familiar with these as they are used heavily in gatsby plugins.

Here is a sample of the block used to parse markdown.

// Converts markdown to VNodes in hyperscript
export const toVNodes = (content: string) => {
  let counter = 0;
  const pipeline = unified()
    .use(markdown as any, { commonmark: true })
    .use(externalLinks, { target: "_blank", rel: ["nofollow"] })
    .use(frontmatter, "yaml")
    .use(remark2rehype)
    .use(slug)
    .use(rehypePrism);

  const nodes = pipeline.parse(content);
  const result = pipeline.runSync(nodes);
  return toH(
    (tag: string, props: any, children: any[]) =>
      v(tag, { ...props, key: counter++ }, children),
    result
  );
};

Enter fullscreen mode Exit fullscreen mode

This block function uses unified to parse a markdown file. The result of parsing this file is a markdown abstract tree that is then passed through a series of remark plugins to transform that markdown into a product that we can then parse to HTML with rehype and some other rehype plugins. Once that is done, we can transform this product to hyperscript using hast-to-hyperscript using the built in dojo virtual dom tooling to produce the needed nodes.

Routing

I wasn't just building a static site generator for this blog. I was porting my existing wordpress blog to a static site. So I wanted to make sure that all the existing links out there would still work, so I had to mimic the existing structure. To do this, my routes look like this.

export default [
  {
    path: "/{path}",
    outlet: "blog",
    defaultRoute: true
  }
];

Enter fullscreen mode Exit fullscreen mode

The root of the route would be / and blog posts links would go to /{path}. I wanted the home page to consist of a list of the blog posts with the published date and descriptions. So I made a card widget to display these.

export default class Card extends WidgetBase<CardProperties> {
  protected render() {
    const { title, date, description, path, cover_image } = this.properties;
    return (
      <section classes={[css.root]}>
        <div classes={[css.column]}>
          <Link
            to="blog"
            params={{
              // markdown is in a posts/ folder with extension .md
              // so clean that up
              path: path.replace("posts/", "").replace(".md", "")
            }}
          >
            {title}
          </Link>
          <p>{dateFormatter(new Date(date))}</p>
          <span>{description}</span>
          <br />
          <Link
            to="blog"
            params={{
              path: path.replace("posts/", "").replace(".md", "")
            }}
          >
            READ MORE
          </Link>
        </div>
        <div classes={[css.column]}>
          <img classes={[css.image]} src={cover_image} />
        </div>
      </section>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

I'm using the metadata from each blog post to create these cards. I'm using a lot of the front matter for metadata that dev.to uses, because it will make it easier for me to cross-post there as well. Like I'm doing with this post!

The result is a card that looks similar to this.

dojo blog card

Templates

Blog posts are represented as templates. In this case they can render in a card styles for the main page or as an entire blog post. The blog post template looks like this.

export default class BlogPost extends WidgetBase<PostProperties> {
  protected render() {
    let { excerpt = false, path } = this.properties;
    if (!path.includes(".md")) {
      path = `${path}.md`;
    }
    // compile the blog post content
    const post: any = this.meta(Block).run(compileBlogPost)({
      path
    });
    if (post) {
      const date = dateFormatter(new Date(post.meta.date));
      // if displayed as a card, just return the content in card format
      if (excerpt) {
        return <Card path={path} {...post.meta} />;
      }
      // or return the content as a full blog post
      return (
        <Content key={post.meta.title}>
          {!excerpt && <img src={post.meta.cover_image} />}
          <Link
            to="blog"
            params={{
              path: path.replace("posts/", "").replace(".md", "")
            }}
          >
            <h2>{post.meta.title}</h2>
          </Link>
          <p>
            {post.meta.author} | {date}
          </p>
          {post.content}
        </Content>
      );
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

The results of this looks just like my blog here.

Building

In order for the static pages to be built, I need to configure my .dojorc correctly with the routes to all my blog posts. Note that "." is how I can tell the dojo build-time-render to build a static index page.

{
  "build-app": {
    "build-time-render": {
      "root": "root",
      "paths": [
        "build-time-rendering-in-dojo",
        "building-a-simple-app-in-dojo",
        "build-static-site-with-dojo",
        "creating-a-datepicker-with-dojo",
        "dojo-cli-template-app",
        "dojo-containers",
        "dojo-from-the-blocks",
        "intro-to-the-dojo-registry",
        "intro-to-the-dojo-router",
        "maintain-state-with-dojo-stores",
        "style-dojo-widgets",
        "testing-with-dojo",
        "up-and-running-with-dojo-cli",
        "watch-for-property-changes-in-widgets",
        "web-components-with-dojo",
        "."
      ],
      "puppeteerOptions": {
        "args": ["--no-sandbox", "--disable-setuid-sandbox"]
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

I'm planning on automating the updates of the .dojorc with a script that I can run before the build is run, but I haven't gotten that far yet.

Once it's been built, the result is a static website with subdirectories to each page I've built.

dojo build time render result

This means that even if someone is on a slow connection or the javascript doesn't load correctly, my site should still be visible, including the routes of my site.

Deployment

Since my site doesn't require making any API calls or rely on a server to do any server side rendering, it's fairly easy to deploy to any number of your favorite hosting services. For the record, I tried to deploy to zeit and netlify and they both appeared to work great at first. However, it looks like the subdirectories of my dist directory would not deploy, so links to pages other than the main page would not work. If I linked to a page from the main page, dojo routing worked fine, but not when using the URL. I'm sure I just did not configure something correctly, but it wasn't clear to me what I didn't do right.

So at the end of the day, I deployed to aws s3. Although, s3 configuration isn't exactly simple, I know enough to drag and drop the folders over and I could even set up a pipeline from github. I'll probably need to add a published tag to my posts like dev.to does so I an push posts in progress to github without deploying them with the rest of the site.

There is plenty more I want to do with this project going forward!

For web apps

My blog is not a full blown web application, but that doesn't mean you can't take the same static site generating tools of dojo to build a mostly static site that will also fetch data from external sources and use them both to build powerful web applications. The static site generating tools are just one piece of a larger system of powerful features built in to dojo.

Try it yourself

If you want to try this dojo static site generator out yourself, you can quickly get started with the following command.

npx degit odoe/btr-site my-awesome-site
Enter fullscreen mode Exit fullscreen mode

You can check out this starter project here!

GitHub logo odoe / btr-site

Template static blog site built with Dojo

Dojo BTR Static Blog

This project was generated with the Dojo CLI & Dojo CLI create app command.

Blog site template, heavily copied influenced from dojo/site repo Built to be a static site tool. Still a work in progress.

Demo

Build

Run npm run build or dojo build --mode dist (the mode option defaults to dist) to create a production build for the project. The built artifacts will be stored in the output/dist directory.

Development Build

Run npm run build:dev or dojo build --mode dev to create a development build for the project. The built artifacts will be stored in the output/dev directory.

Development server

Run npm run dev or dojo build --mode dev --watch file --serve to create a development build and start a development server. By default the server runs on port 9999, navigate to http://localhost:9999/.

To change the port of the development…

Summary

I've had a lot of fun putting this project together and learned a lot about how unified, remark, rehype, and other plugins work, as well as how to really use dojo blocks to do some interesting things. I may not have all the bells and whistles of wordpress, but I don't think I fully need them. Anything substantial that wordpress could tell me, I can get from google analytics and I'm much more comfortable just publishing in markdown anyway. It also won't hurt to save a few bucks on that digital ocean bill :)

Top comments (0)

Visualizing Promises and Async/Await 🀯

async await

☝️ Check out this all-time classic DEV post