SvelteKit starter blog with GraphCMS
I made a starter blog with SvelteKit and GraphCMS, I’ve been doing a couple of small projects with it and I really like it!
This will focus mainly on the Svelte and SvelteKit side with not too much emphasis on the GraphCMS side. GraphCMS has a blog starter that you can spin up real quick to get started using the platform.
Create the backend with GraphCMS
First up I’ll create a GraphCMS project for this starter to pull the posts from, if you’re following along you can crate a GraphCMS account either with email or by logging in with your GitHub account.
Once you’re signed in you can either create a new project from scratch or pick one of the starters that are available.
I’m going to pick the “Blog Starter” project which will have GraphCMS schema already created with some content to use.
Next I’m prompted to give my project a name and a description then I’ll need to pick the (data center) region where I want the project to live, I’m picking Europe as that closest to me then clicking the “Create Project” button.
I’m then prompted to pick the pricing plan for the project, as this is only a demo I’ll pick the community pricing plan (free) and skip inviting team members.
Scaffold out the SvelteKit project
Now I’ll create a directory for the project change directory into it
(cd
) and use the SvelteKit npm init
script:
1mkdir sveltekit-starter-blog2cd sveltekit-starter-blog3# initialise SvelteKit4npm init svelte@next
I chose the skeleton project option with no TypeScript support and ESLint and Prettier config. Now I can install the dependencies with:
1npm i
I can now start the dev server and take a look at the project, I’ll
start the dev server with npm run
and add some additional
paramerters to the script with --
then -o
(or --open
) to open in
the browser and -p 3300
(or --port 3300
) to specify the port to
run on:
1npm run dev -- -o -p 3300
That’s the project, pretty bare bones with nothing other than a landing page!
Add TailwindCSS
There’s a really useful GitHub project that adds additional functionality to your Svelte projects with Svelte Add in this case I’m going to add Tailwind with the Just In Time (JIT) compiler enabled:
1npx svelte-add tailwindcss --jit
Then I’ll need to create the base CSS file and add in the Tailwind
directives for base
, components
, and utilities
:
1touch src/app.css
Add in the Tailwind directives to the src/app.css
file:
1@tailwind base;2@tailwind components;3@tailwind utilities;
I’ll come onto the styling later for now I’m going to focus on getting the posts into the project and using the SvelteKit layout to add some elements that are presisted through route changes.
Create a SvelteKit layout in the routes folder:
1touch src/routes/__layout.svelte
The layout will be wrapping the project so the one requirement of a
__layout.svelte
in SvelteKit is a <slot/>
see my notes on Svelte
slots for more on that.
In the layout I’ll import the global CSS and add in a basic nav for now along with the required slot:
1<script>2 import '../app.css'3</script>4
5<nav>6 <a href="/">Home</a>7 <a href="/about">About</a>8</nav>9
10<slot />
Get the posts from GraphCMS
First up I’ll list out the posts from the GraphCMS GraphQL endpoint into the index page (home page) of the project so that there’s a list of clickable links to take the user to the post.
I’ll be using graphql-request
to query the posts so I’ll install
that along with graphql
:
1npm i -D graphql-request graphql
Here’s what I want graphql-request
to do for me, this example is
taken from the prisma labs example documentation with the required
query from my project:
1import { gql, GraphQLClient } from 'graphql-request'2
3export async function load() {4 const graphcms = new GraphQLClient(5 import.meta.env.VITE_GRAPHCMS_URL,6 {7 headers: {},8 }9 )10
11 const query = gql`12 query Posts {13 posts {14 id15 title16 slug17 date18 excerpt19 }20 }21 `22
23 const { posts } = await graphcms.request(query)24
25 return {26 props: {27 posts,28 },29 }30}
Note line 5 where it’s requiring the endpoint, I’ll need to create an
.env
file for that and define the VITE_GRAPHCMS_URL
note that the
env
variable is prefixed with VITE_
to expose it to the client,
you can check out the SvelteKit faq for more info:
1touch .env
I’ll need to get the GraphQL endpoint from GraphCMS for
graphql-request
. So from my GraphCMS dashboard for my project
there’s a navigation bar on the leftand toward the bottom there’s a
settings button. Clicking that for the settings panel then under
“Access” there’s an “API Access” page that will show the URL endpoint.
One thing I’ll need to do before copying the URL endpoint is set the API access permissions, I’ll only want posts from the published stage so I’m going to check the “Content from stage Published” checkbox.
CLicking on the URL endpoint on the settings screen will copy it to my
clipboard I can then add that to the .env
file:
1VITE_GRAPHCMS_URL=https://api-eu-central-1.graphcms.com/v2/projectid/master
I’m using Svelte script context="module"
so that that I can get the
posts from the GraphCMS endpoint ahead of time. That means it’s run
when the page is loaded and the posts can be loaded ahead of the page
(component) being rendered on the screen:
1<script context="module">2 import { gql, GraphQLClient } from 'graphql-request'3
4 export async function load() {5 const graphcms = new GraphQLClient(6 import.meta.env.VITE_GRAPHCMS_URL,7 {8 headers: {},9 }10 )11
12 const query = gql`13 query Posts {14 posts {15 id16 title17 slug18 date19 excerpt20 }21 }22 `23
24 const { posts } = await graphcms.request(query)25
26 return {27 props: {28 posts,29 },30 }31 }32</script>33
34<script>35 export let posts36</script>37
38<svelte:head>39 <title>SvelteKit starter blog</title>40</svelte:head>41
42<h1 class="text-3xl">SvelteKit starter blog</h1>43<ul>44 {#each posts as post}45 <li>46 <a class="text-blue-600 underline" href="/post/{post.slug}">47 {post.title}48 </a>49 </li>50 {/each}51</ul>
Breaking this down a bit, the props: {posts}
(or props.posts
) that
are being returned by the graphql-request
in the Svelte module are
then accepted as props to the page with:
1<script>2 export let posts3</script>
I’m then using Svelte {#each}
to loop through (over, round,
whatever) the posts returned from the GraphQL query made in the
graphql-request
with some minimal Tailwind classes.
Now I have the projects listed I can click on the links but they don’t go anywhere and the error message isn’t great either, I’ll fix that by making an error page.
Adding an error page
I’ll create a basic error page to show some information to the user,
first up create the page in the routes
folder:
1touch src/routes/__error.svelte
Then add in the error message and status:
1<script context="module">2 export function load({ error, status }) {3 return {4 props: { error, status },5 }6 }7</script>8
9<script>10 export let error, status11</script>12
13<svelte:head>14 <title>{status}</title>15</svelte:head>16
17<h1>{status}: {error.message}</h1>
Now clicking one of the home page links will give a nicer looking error.
Routes for the blog posts
If you are familiar with NextJS dynamic routes then this next part will be familiar, if not then it’s a way to determine the routes (URL address) in the project programmatically.
1mkdir src/routes/post/2# note the quotes around the path here 👇3touch 'src/routes/post/[slug].svelte'
Now in the [slug].svelte
file I can do pretty much the same to query
the GraphQL endpoint on GraphCMS for a single post, I’ll specify the
post slug
to bring display the post relating to it.
The query looks like this:
1query PostPageQuery($slug: String!) {2 post(where: { slug: $slug }) {3 title4 date5 content {6 html7 }8 tags9 author {10 id11 name12 }13 }14}
This looks a little different to the query on the index page as I’m
specifying a single post with the $slug
variable.
To get the $slug
variable I’ll pull that from the context
parameter that is passed to the load
function:
1<script context="module">2 import { gql, GraphQLClient } from 'graphql-request'3
4 export async function load(context) {5 const graphcms = new GraphQLClient(
That can be used to get the .page.params.slug
which is what I can
add to the variables object for the graphql-request
.
1<script context="module">2 import { gql, GraphQLClient } from 'graphql-request'3
4 export async function load(context) {5 const graphcms = new GraphQLClient(6 import.meta.env.VITE_GRAPHCMS_URL,7 {8 headers: {},9 }10 )11
12 const query = gql`13 query PostPageQuery($slug: String!) {14 post(where: { slug: $slug }) {15 title16 date17 content {18 html19 }20 tags21 author {22 id23 name24 }25 }26 }27 `28
29 const variables = {30 slug: context.page.params.slug,31 }32
33 const { post } = await graphcms.request(query, variables)34
35 return {36 props: {37 post,38 },39 }40 }41</script>42
43<script>44 export let post45</script>46
47<svelte:head>48 <title>{post.title}</title>49</svelte:head>50
51<h1>{post.title}</h1>52<p>{post.author.name}</p>53<p>{post.date}</p>54<p>{post.tags}</p>55{@html post.content.html}
Take note of the last line here where I’m using the Svelte @html
tag, this renders content with HTML in it.
It will take this:
1<p>This is a blog post paragraph.</p>2<p></p>3<p>This is another a blog post paragraph.</p>
And render it to:
1This is a blog post paragraph.2
3This is another a blog post paragraph.
Svelte doesn’t sanitise anyting inside the {@html ...}
so make sure
that you trust the source of that data or you risk exposing the users
to XSS attacks.
Style it
Now I’ve covered displaying a list of content and using dynamic routes with SvelteKit it’s time to make it not look like crap! 😂
I’ll be using Tailblocks for the styling elements, rather than dump out all the HTML here what I have done is made an example repo for reference or use as a starter.
Conclusion
I’m still early days with Svelte but so far I’m super impressed with it. This was an example with GraphCMS for the backend you can use Ghost, Sanity or whatever else fits your fancy.
Back to Top