Why (and how) I Moved my Personal Blog From Gatsby to Sapper (WIP)

Hey, so here goes the reason...

Gatsby is an awesome CMS, and works great with Netlify CMS, no question about that. And I'm pretty sure you can have your Gatsby webapp working really fine on Netlify. So why switch to something else?

In my case, I probably shot myself in the foot trying to use Reason and ReasonReact with Gatsby because of a bunch of reasons...

  • The Gatsby Webpack config isn't that transparent, and custom Webpack configs have always been painful
  • I didn't find a simple way to push the already built Bucklescript "world" compiled files. Because it's not only the Reason files in the source code, it's also node modules dependencies written in Reason, that didn't go well with Gatsby's Webpack config
  • I had to add bs-platform as a dependency so Netlify would build the Gatsby webapp without crashing
  • bs-platform is a great tool but heavy as hell
  • My Netlify deployment's node build command runs out of memory and crashes
  • No more webapp updates, no more posts, nothing

I've had my eyes on Svelte since the v3 release, never really tried it, so with all that frustration built from code too heavy, I took that drastic decision to move everything to the opposite, Sapper, the Svelte equivalent of React's Next.js, and to add the Netlify CMS on top of it.

Now the how...

I mostly followed what that fellow, spiffy, describes in his blog, with minor but crucial changes.

First, create a project with Sapper and stuff

npx degit "sveltejs/sapper-template#rollup" my-app

(Cool tools, right!)

Create static/admin, static/uploads and static/_posts folders, for the CMS admin interface, media uploads, and blog post directory, respectively.

Create a static/admin/index.html file with the following content

<!doctype html>  
<html>  
  <head>  
    <meta charset="utf-8" />  
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />  
    <title>Content Manager</title>  
  </head>  
  <body>  
    <script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>  
    <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>  
  </body>  
</html>  

And now the static/admin/config.yml.

backend:  
  name: git-gateway  
  branch: master  

media_folder: static/uploads  
public_folder: /uploads  

collections:  
  - name: "blog"  
    label: "Blog"  
    folder: "static/_posts"  
    create: true  
    slug: "{{year}}-{{month}}-{{day}}-{{slug}}"  
    fields:  
      - {label: "Template Key", name: "templateKey", widget: "hidden", default: "blog-post"}  
      - {label: "Title", name: "title", widget: "string"}  
      - {label: "Publish Date", name: "date", widget: "datetime"}  
      - {label: "Description", name: "description", widget: "text"}  
      - {label: "Body", name: "body", widget: "markdown"}  
      - {label: "Tags", name: "tags", widget: "list"}  

And the index (root) page src/routes/index.svelte.

<svelte:head>  
    <title>Sapper project template</title>  
    <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>  
</svelte:head>  

... And also that script in the same file:

<script>
  import { onMount } from 'svelte';

  onMount(() => {
    if (window.netlifyIdentity) {
      window.netlifyIdentity.on("init", user => {
        if (!user) {
          window.netlifyIdentity.on("login", () => {
            document.location.href = "/admin/";
          });
        }
      });
    }
  });
</script>

Now install dependencies to read MarkDown blog posts.

npm install mz glob markdown-it front-matter

And the blo post fetching in JSON:

import fm from 'front-matter';
import glob from 'glob';
import {fs} from 'mz';
import path from 'path';

export async function get(req, res) {
  // List the Markdown files and return their filenames
  const posts = await new Promise((resolve, reject) =>
      glob('static/_posts/*.md', (err, files) => {
      if (err) return reject(err);
      return resolve(files);
    }),
  );

  // Read the files and parse the metadata + content
  const postsFrontMatter = await Promise.all(
    posts.map(async post => {
      const content = (await fs.readFile(post)).toString();
      // Add the slug (based on the filename) to the metadata, so we can create links to this blog post
      return {...fm(content).attributes, slug: path.parse(post).name};
    }),
  );

  // Sort by reverse date, because it's a blog
  postsFrontMatter.sort((a, b) => (a.date < b.date ? 1 : -1));

  res.writeHead(200, {
    'Content-Type': 'application/json',
  });

  // Send the list of blog posts to our Svelte component
  res.end(JSON.stringify(postsFrontMatter));
}

Remove some unused files src/routes/blog/_posts.js and src/routes/blog/[slug].json.js.

To be continued...