Introduction
Hello.
In this post, I'll be outlining how to get started with Svelte, Sapper and Netlify CMS.
This article assumes some baseline knowledge of Svelte, Sapper and various configuration options specific to Netlify's CMS.
Documentation
You can find the repo for this project here.
What we'll accomplish
- Setup a Svelte / Sapper project
- Setup a Netlify Project + Netlify Authentication
- Configure Netlify to automatically build & deploy to Github
- Refactor Sapper to statically generate blog posts from Markdown
Setting up Svelte & Sapper
The team at Sapper has setup a great starter template that we'll use to skip a lot of the tedious aspects of starting from scratch.
It's also pretty damn un-opinionated so even if you decide to grow this into a larger project, you won't be locked into anything.
We'll be opting to use the Rollup based bundler since at the time of this writing it is better documented for use with Sapper.
npx degit "sveltejs/sapper-template#rollup" my-app
cd
into my-app
and run
npm i && npm run dev
You should see your console output
> Listening on http://localhost:3001
Open http://localhost:3001
in your browser and take a peek.
Now that we're up and running, we can start getting things organized in our code to get everything linked up to Netlify.
Setup Netlify + Netlify Authentication
First we'll have to create a new folder within ~/static
called admin
. Therein we'll create two files, config.yml
and index.html
.
First, let's drop in a simple config for Netlify's CMS so we can outline how we'll structure our blog post entries:
# ~/static/admin/config.yml
backend:
name: git-gateway
branch: master # Branch to update (optional; defaults to master)
publish_mode: editorial_workflow # Allows you to save drafts before publishing them
media_folder: static/uploads # Media files will be stored in the repo under static/images/uploads
public_folder: /uploads # The src attribute for uploaded media will begin with /images/uploads
collections:
- name: "blog" # Used in routes, e.g., /admin/collections/blog
label: "Blog" # Used in the UI
folder: "static/posts" # The path to the folder where our blog posts are stored
create: true # Allow users to create new documents in this collection
fields: # The fields for each document
- { label: "Slug", name: "slug", widget: "string" }
- { label: "Title", name: "title", widget: "string" }
- { label: "Body", name: "body", widget: "markdown" }
Next, let's add the markup for the /admin
route:
<!-- ~/static/admin/index.html -->
<!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>
<!-- Include the script that builds the page and powers Netlify CMS -->
<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>
Refactoring for Markdown
If you're not familiar with how Sapper handles dynamic URL parameters, check out their documentation on routing.
We'll be editing our ~/src/routes/blog/[slug].json.js
to read markdown files from the filesystem, parse the Markdown + Frontmatter, and render the data into our component.
We will also need to edit ~/src/routes/blog/index.json.js
to extract the various titles and slugs from our posts in order to display them on the /blog
route.
For this, we'll make use of gray-matter to handle the Frontmatter which is in YAML and marked to parse our Markdown.
Install these two dependencies via npm:
npm i marked gray-matter
Let's also create a folder where our blog posts will live. Create a folder called posts
within ~/static
. We told Netlify to save posts here with the line
folder: "static/posts"
in our config.yaml
for our blog collection.
Next, let's setup our [slug].json.js
file to put these two libraries to use:
// ~/src/routes/blog/[slug].json.js
import path from "path";
import fs from "fs";
import grayMatter from "gray-matter";
import marked from "marked";
const getPost = (fileName) => {
return fs.readFileSync(
path.resolve("static/posts/", `${fileName}.md`),
"utf-8"
);
};
export function get(req, res, _) {
const { slug } = req.params;
const post = getPost(slug);
const renderer = new marked.Renderer();
const { data, content } = grayMatter(post);
const html = marked(content, { renderer });
if (html) {
res.writeHead(200, {
"Content-Type": "application/json",
});
res.end(JSON.stringify({ html, ...data }));
} else {
res.writeHead(404, {
"Content-Type": "application/json",
});
res.end(
JSON.stringify({
message: `Not found`,
})
);
}
}
Next we'll modify our ~/src/routes/blog/index.json.js
file to read all the files within our ~/static/posts
directory and pluck out the information required to render and provide links to each article.
// ~/src/routes/blog/index.json.js
import fs from "fs";
import path from "path";
import grayMatter from "gray-matter";
const getAllPosts = () => {
try {
return fs.readdirSync("static/posts/").map((fileName) => {
const post = fs.readFileSync(
path.resolve("static/posts", fileName),
"utf-8"
);
return grayMatter(post).data;
});
} catch (e) {
return [];
}
};
export function get(_, res) {
res.writeHead(200, {
"Content-Type": "application/json",
});
const posts = getAllPosts();
res.end(JSON.stringify(posts));
}
Since we're no longer using the original data source for the blog posts ~/src/routes/blog/_posts.js
we can delete that file.
Also, since we're passing our JSON data from [slug].json.js
to [slug].svelte
with the same structure as before, we don't need to make any changes to the latter file.
Setting up Netlify & Git Repo
At this point, we've nearly got our ducks in a row to deploy our site and start writing blog posts.
First, create a new repo and push your code to it.
Next, head on over to Netlify and click 'New Site from Git', select your Git provider of choice, authorize the Netlify App and allow access to all or for more granularity, select the repos you want Netlify to have access to.
Make sure you specify the build command and publish directory like so and mash that 'Deploy Site' button.
If you head back to your Netlify dashboard you should see that your site is building and as soon as it's published, you can preview a link to the site.
Last but not least, we need to enable the Identity and Git Gateway features so you can signup/login via the /admin
path on your newly deployed site to manage posts as well as allow Netlify to publish changes to your Git repo to trigger new static builds.
Logging into the CMS
Head on over to your live site and add the /admin
path to your URL.
Click 'Sign Up', create an account, confirm your account via the automated email from Netlify and jump back over to /admin
and give it a refresh.
Login with your account and get writing.
###
Thanks for reading. If you’d like to stay up to date with my writings and projects, please follow me on Twitter or consider supporting my writing by buying me a coffee.
Top comments (14)
Great article, a few small errors:
The files should be
index.html
andconfig.yml
.And in
[slug].json.js
the lineconst { data, content } = grayMatter(post);
should beconst { data, content } = grayMatter(post).data;
Thanks so much for noting these! I made edits to the post to reflect these corrections 👍
That's actually wrong --> this line breaks things :
const { data, content } = grayMatter(post).data;
because grayMatter(post) is returning:
{ content: 'Testing',
data: { slug: 'test', title: 'Test' },
isEmpty: false,
excerpt: '',
orig:
0d 0a 54 65 73 74 69 6e 67> }
so if you tell it to just return greyMatter(post).data then
content
never gets definedHey Andrew, this is correct. I had a field in my config.yml called content which provided the content for my posts so all the data I needed was inside greyMatter(post).data and I destructured it all out of there. I'm not using the exact same setup so my data (including content) was all inside of data.
Hi, I'm getting this issue with the blog posts :
Unexpected token m in JSON at position 0
SyntaxError: Unexpected token m in JSON at position 0
Hey been awhile since I thought of this post and came back since I was getting the same error randomly after I created a post filtering method that ran in the onMount hook based on which category was selected. Almost pulled all my hair out until I realized it was only rendering JSON for the 2 posts I was showing separately on the front page of my site.
Then it hit me, Sapper only renders routes for links it can follow during export. My post filter (as cool as it is) prevents Sapper from seeing those links during export. I assume this is only an issue with exported sites since all routes worked in dev even before my fix.
My workaround for this was to create an each block and loop through all my posts inside a div that was screen reader only (so it doesn't show on the front end, I also gave them tabindex="-1" just in case so they don't jump into tab order) having this hidden list of links allows Sapper to see them while it's preparing the export and make a route and the accompanying JSON file. Still it's a shit error message especially considering what it really means is that there was no route or JSON at all.
I am sure glad that this "hit you", because I don't have a lot of hair to pull out. :-)
I was so confused that some blog posts (the ones where the title and slug matched) were not getting an error, and other posts (where slug and title did not match) were showing a 500 error.
Thanks for your help!
Hi, thnx for your elaborate explaination, it very insightfull for a beginner like me. Do you have a code snippet for this proposed solution ? I can't figured it out. Whatever I do it keeps spitting the same error.
For sure, here's the exact code I use to avoid the JSON error in a live Netlify + Sapper project. In this code I'm pulling in all posts through the prefetch and filtering, for me the filter was preventing Sapper from rendering any blog routes that were not loaded somewhere else. I put this code in the same file where my filter is but it can go in any .svelte file.
I'm using the class
sr-only
to hide from everyone but screen readers, there are plenty options for this class but the flavor I use in my global css is this.You might also want to put a heading on top of that list of links so it's not confusing for screen readers.
I'm getting the same error.
I manually switched the name of the .md file from the title to slug which allowed me to get to the article and it seems @benjamin's suggestion doesn't work
I hit the same problem and setting the
identifier_field
didn't work for me either.Great post!
One thing threw me so sharing in case it helps @triptych and others:
Netlify CMS will use the
title
field as theslug
and this will cause aJSON
error as Sapper will look for a markdown file with the wrong name.Add
identifier_field: "slug"
to theconfig.yml
undercollections:
Fantastic article! I have one addition - you have to configure the confirmation email to go to either a special location or you have to add the widget to your head --> You might want to add some information about this in a follow up post or edit this one community.netlify.com/t/common-iss...