Table of Contents
  1. Overview
  2. Install Local By FlyWheel
  3. Configure Local
    1. Open up the app and click the + icon in the lower-left corner
    2. Type your name of site you want to build
    3. Check preffered option and click continue
    4. Type your wordpress username, password, email and click Add Site
  4. Access wp-admin dashboard from web browser
    1. Create a couple of dummy posts in the wp-admin dashboard
  5. Install WPGraphQL plugin
  6. Turn WordPress backend as GraphQL Server using WPGraphQL plugin
  7. Set up a new SvelteKit project
  8. Configure CSS and layout
    1. Create a layout css file to globally apply css rules.
    2. Create a layout svelte file
  9. Connect the front-end to wordpress GraphQL server
    1. Create an environment variable for graphql endpoint
  10. Blog Page
    1. Run a graphql query from GraphiQL IDE in the wordpress admin panel
    2. Create a svelte file to render a Blog page
    3. Test a Blog page
  11. Make card component
  12. Individual Post Page
    1. Develop a graphql query
    2. Make a svelte file to render each post.
    3. Test a Post page
  13. Adding a Podcast Player
SvelteKit With Headless WordPress as CMS

Overview

This article will show you how to get started with development of SvelteKit front-end web application using headless WordPress as a CMS(Content Management System). We will also use GraphQL as our query language tool to retrieve data from wordpress.

Install Local By FlyWheel

Local By FlyWheel is a local web server that allows you to develop your website on your local machine instead of a staging or live server.

Instruction

Configure Local

Open up the app and click the + icon in the lower-left corner

local by flywheel configuration

Type your name of site you want to build

local by flywheel configuration

Check preffered option and click continue

local by flywheel configuration

Type your wordpress username, password, email and click Add Site

local by flywheel configuration

If everything goes well, you will be able to see the following information regarding the configured local server.

local by flywheel configuration

Please click Admin button in the top right corner to open up wp-admin login page in the web browser.

Access wp-admin dashboard from web browser

Use your username and password that you've set above to log in to wordpress admin panel.

local by flywheel configuration
local by flywheel configuration

Create a couple of dummy posts in the wp-admin dashboard

Click Posts and Add New. Creating a post is very intuitive and make sure you also set featuredImage so that we can render an image for each post in the front-end application.

local by flywheel configuration
local by flywheel configuration
local by flywheel configuration

Install WPGraphQL plugin

local by flywheel configuration
local by flywheel configuration

Click Install button and then Activate button as well.

Turn WordPress backend as GraphQL Server using WPGraphQL plugin

Now you will be able to see GraphQL menu from the left panel. Hover your mouse over the GraphQL menu and click Settings:

local by flywheel configuration

You will be able to find your graphql endpoint:

In my case, it is http://sveltekitwordpressdemo.local/graphql. This endpoint will be used later in SvelteKit to make our HTTP POST request to send our graphql query and retreive data we want.

local by flywheel configuration

Now that we've set up everything we need in the local wordpress server, let's get started with creating a new SvelteKit project.

Set up a new SvelteKit project

Terminal
npm init svelte@next sveltekit-wordpress-demo
cd sveltekit-wordpress-demo
npm install
npm run dev -- --open

Configure CSS and layout

Create a layout css file to globally apply css rules.

./src/layout.css
1:root {
2 --border-radius: 8px;
3
4 /* Colors */
5 --color-white: #fff;
6 --color-gray-1: #102a43;
7 --color-gray-2: #243b53;
8 --color-gray-3: #334e68;
9 --color-gray-4: #486581;
10 --color-gray-5: #627d98;
11 --color-gray-6: #829ab1;
12 --color-gray-7: #9fb3c8;
13 --color-gray-8: #bcccdc;
14 --color-gray-9: #d9e2ec;
15 --color-gray-10: #f0f4f8;
16 --color-yellow-1: #8d2b0b;
17 --color-yellow-2: #b44d12;
18 --color-yellow-3: #cb6e17;
19 --color-yellow-4: #de911d;
20 --color-yellow-5: #f0b429;
21 --color-yellow-6: #f7c948;
22 --color-yellow-7: #fadb5f;
23 --color-yellow-8: #fce588;
24 --color-yellow-9: #fff3c4;
25 --color-yellow-10: #fffbea;
26
27 /* Box Shadows */
28 --box-shadow-1: rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, rgba(0, 0, 0, 0.14) 0px 1px
29 1px 0px, rgba(0, 0, 0, 0.12) 0px 1px 3px 0px;
30 --box-shadow-2: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px
31 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px;
32 --box-shadow-3: rgba(0, 0, 0, 0.2) 0px 3px 3px -2px, rgba(0, 0, 0, 0.14) 0px 3px
33 4px 0px, rgba(0, 0, 0, 0.12) 0px 1px 8px 0px;
34 --box-shadow-4: rgba(0, 0, 0, 0.2) 0px 2px 4px -1px, rgba(0, 0, 0, 0.14) 0px 4px
35 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px;
36 --box-shadow-5: rgba(0, 0, 0, 0.2) 0px 3px 5px -1px, rgba(0, 0, 0, 0.14) 0px 5px
37 8px 0px, rgba(0, 0, 0, 0.12) 0px 1px 14px 0px;
38 --box-shadow-6: rgba(0, 0, 0, 0.2) 0px 3px 5px -1px, rgba(0, 0, 0, 0.14) 0px 6px
39 10px 0px, rgba(0, 0, 0, 0.12) 0px 1px 18px 0px;
40 --box-shadow-7: rgba(0, 0, 0, 0.2) 0px 4px 5px -2px, rgba(0, 0, 0, 0.14) 0px 7px
41 10px 1px, rgba(0, 0, 0, 0.12) 0px 2px 16px 1px;
42 --box-shadow-8: rgba(0, 0, 0, 0.2) 0px 5px 5px -3px, rgba(0, 0, 0, 0.14) 0px 8px
43 10px 1px, rgba(0, 0, 0, 0.12) 0px 3px 14px 2px;
44 --box-shadow-9: rgba(0, 0, 0, 0.2) 0px 5px 6px -3px, rgba(0, 0, 0, 0.14) 0px 9px
45 12px 1px, rgba(0, 0, 0, 0.12) 0px 3px 16px 2px;
46 --box-shadow-10: rgba(0, 0, 0, 0.2) 0px 6px 6px -3px, rgba(0, 0, 0, 0.14) 0px 10px
47 14px 1px, rgba(0, 0, 0, 0.12) 0px 4px 18px 3px;
48 --box-shadow-11: rgba(0, 0, 0, 0.2) 0px 6px 7px -4px, rgba(0, 0, 0, 0.14) 0px 11px
49 15px 1px, rgba(0, 0, 0, 0.12) 0px 4px 20px 3px;
50 --box-shadow-12: rgba(0, 0, 0, 0.2) 0px 7px 8px -4px, rgba(0, 0, 0, 0.14) 0px 12px
51 17px 2px, rgba(0, 0, 0, 0.12) 0px 5px 22px 4px;
52 --box-shadow-13: rgba(0, 0, 0, 0.2) 0px 7px 8px -4px, rgba(0, 0, 0, 0.14) 0px 13px
53 19px 2px, rgba(0, 0, 0, 0.12) 0px 5px 24px 4px;
54 --box-shadow-14: rgba(0, 0, 0, 0.2) 0px 7px 9px -4px, rgba(0, 0, 0, 0.14) 0px 14px
55 21px 2px, rgba(0, 0, 0, 0.12) 0px 5px 26px 4px;
56 --box-shadow-15: rgba(0, 0, 0, 0.2) 0px 8px 9px -5px, rgba(0, 0, 0, 0.14) 0px 15px
57 22px 2px, rgba(0, 0, 0, 0.12) 0px 6px 28px 5px;
58}
59
60html,
61body {
62 padding: 0;
63 margin: 0;
64 font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
65 Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
66 background-color: #f0f4f8;
67 color: var(--color-gray-2);
68}
69
70a {
71 color: inherit;
72}

Create a layout svelte file

./src/routes/__layout.svelte
1<script context="module">
2 import '../layout.css';
3</script>
4
5<main>
6 <slot />
7</main>
8
9<style>
10 main {
11 padding: 2rem;
12 max-width: 800px;
13 margin: 0 auto 12rem;
14 }
15</style>

Connect the front-end to wordpress GraphQL server

Create an environment variable for graphql endpoint

Create .env.local file in your project root directory and type VITE_PUBLIC_WORDPRESS_API_URL=. Then copy and paste the graphql endpoint that we've seen in the WPGraphQL Settings in the wp-admin panel.

local by flywheel configuration

Blog Page

Run a graphql query from GraphiQL IDE in the wordpress admin panel

When you work with graphql, a typical way to develop is:

  1. Make a query in the GraphiQL IDE tool and make sure it returns a resulting data.
  2. Then you will copy the query and use it as a query variable in the one of those Svelte files that will render a page in your web application.

Go back to wp-admin panel and open up GraphiQL IDE and click Query Composer. You can drill down posts item and select the items as they appear in the following screen capture. Once you selected items you want, you can click the play button to see the resulting data that we would like to want from our SvelteKit front-end application.

local by flywheel configuration

Create a svelte file to render a Blog page

Now let's create a svelte file to render our Blog page. Create ./src/routes/blog/index.svelte file that will make a HTTP POST request to send a graphql query and receive a resulting blog posts data and then render a list of the blog post titles.

./src/routes/blog/index.svelte
1<script context="module">
2 const query = `
3 query getPosts {
4 posts {
5 nodes {
6 databaseId
7 uri
8 title
9 excerpt
10 date
11 featuredImage {
12 node {
13 sourceUrl
14 altText
15 mediaDetails {
16 width
17 height
18 }
19 }
20 }
21 }
22 }
23 }
24 `;
25 export async function load({ url, fetch }) {
26 const response = await fetch(import.meta.env.VITE_PUBLIC_WORDPRESS_API_URL, {
27 method: 'POST',
28 headers: {
29 'Content-Type': 'application/json',
30 },
31 body: JSON.stringify({ query }),
32 });
33 if (response.ok) {
34 const responseObj = await response.json();
35 const posts = responseObj.data.posts.nodes;
36 return {
37 props: {
38 posts
39 }
40 };
41 }
42 return {
43 status: response.status,
44 error: new Error(`Could not load ${url}`)
45 };
46 }
47</script>
48
49<script>
50 export let posts;
51</script>
52
53<h1>Blog</h1>
54{#if posts}
55 <ul>
56 {#each posts as post}
57 <li>
58 <p>{post.title}</p>
59 </li>
60 {/each}
61 </ul>
62{:else}
63 <p>No posts found.</p>
64{/if}
65
66<style>
67 ul {
68 list-style: none;
69 padding: 0;
70 }
71 ul li + li {
72 margin-top: 2rem;
73 }
74</style>

Test a Blog page

Let's run our application in the dev environment:

Terminal
npm run dev -- --open

You will be able to see the blog page loaded when you access localhost:3000/blog

local by flywheel configuration

Make card component

Now that we know that we can retreive data from worpress graphql server, let's make our blog page a bit prettier making PostCard component.

Create a PostCard component:

./src/components/PostCard.svelte
1<script>
2 export let post;
3</script>
4
5<article>
6 {#if post.featuredImage}
7 <a href={`/blog${post.uri}`}>
8 <img src={post.featuredImage.node.sourceUrl} alt={post.featuredImage.node.altText} />
9 </a>
10 {/if}
11 <a href={`/blog${post.uri}`}>
12 <h2>{post.title}</h2>
13 </a>
14 <div>{@html post.excerpt}</div>
15</article>
16
17<style>
18 article {
19 padding: 2.5rem;
20 background-color: var(--color-white);
21 border-radius: var(--border-radius);
22 box-shadow: var(--box-shadow-2);
23 }
24 article > a {
25 text-decoration: none;
26 }
27 article h2 {
28 margin-top: 0;
29 }
30 article img {
31 max-width: 100%;
32 }
33 article h2 {
34 margin-top: 2rem;
35 }
36</style>

Because we want to use this component in the Blog page, we have to make a change in the ./src/routes/blog/index.svelte file:

  • Import PostCard component (line 50)
  • Replace <p> tag with <PostCard {post} /> (line 59, 60)
./src/routes/blog/index.svelte
1<script context="module">
2 const query = `
3 query getPosts {
4 posts {
5 nodes {
6 databaseId
7 uri
8 title
9 excerpt
10 date
11 featuredImage {
12 node {
13 sourceUrl
14 altText
15 mediaDetails {
16 width
17 height
18 }
19 }
20 }
21 }
22 }
23 }
24 `;
25 export async function load({ url, fetch }) {
26 const response = await fetch(import.meta.env.VITE_PUBLIC_WORDPRESS_API_URL, {
27 method: 'POST',
28 headers: {
29 'Content-Type': 'application/json',
30 },
31 body: JSON.stringify({ query }),
32 });
33 if (response.ok) {
34 const responseObj = await response.json();
35 const posts = responseObj.data.posts.nodes;
36 return {
37 props: {
38 posts
39 }
40 };
41 }
42 return {
43 status: response.status,
44 error: new Error(`Could not load ${url}`)
45 };
46 }
47</script>
48
49<script>
50 import PostCard from '../../components/PostCard.svelte';
51 export let posts;
52</script>
53
54<h1>Blog</h1>
55{#if posts}
56 <ul>
57 {#each posts as post}
58 <li>
59 <p>{post.title}</p>
60 <PostCard {post} />
61 </li>
62 {/each}
63 </ul>
64{:else}
65 <p>No posts found.</p>
66{/if}
67
68<style>
69 ul {
70 list-style: none;
71 padding: 0;
72 }
73 ul li + li {
74 margin-top: 2rem;
75 }
76</style>

Now when you check the Blog page again, you should see the blog posts rendered in a card format.

local by flywheel configuration

Individual Post Page

However, we can't click an individual blog card to see the detail post yet. To be able to do that, we need to make a dynamic route that is going to be rendered depending on each slug of the blog post.

Develop a graphql query

You can run the following graphql query from the GraphiQL IDE in the wp-admin panel:

local by flywheel configuration

Make a svelte file to render each post.

Create ./src/routes/blog/[slug].svelte:

./src/routes/blog/[slug].svelte
1<script context="module">
2 export const prerender = true;
3 const query = `
4 query getPostBySlug($slug: ID!) {
5 post(id: $slug, idType: SLUG) {
6 date
7 title
8 content
9 author {
10 node {
11 name
12 }
13 }
14 categories {
15 nodes {
16 name
17 }
18 }
19 featuredImage {
20 node {
21 sourceUrl
22 altText
23 mediaDetails {
24 width
25 height
26 }
27 }
28 }
29 }
30 }
31 `;
32 export async function load({ url, params, fetch }) {
33 const response = await fetch(import.meta.env.VITE_PUBLIC_WORDPRESS_API_URL, {
34 method: 'POST',
35 headers: {
36 'Content-Type': 'application/json',
37 },
38 body: JSON.stringify({
39 query,
40 variables: {
41 slug: params.slug,
42 }
43 }),
44 });
45 if (response.ok) {
46 const responseObj = await response.json();
47 const { post } = responseObj.data;
48 return {
49 props: {
50 post
51 }
52 };
53 }
54 return {
55 status: response.status,
56 error: new Error(`Could not load ${url}`)
57 };
58 }
59</script>
60
61<script>
62 export let post;
63 const formatDate = (date) => new Date(date).toLocaleDateString();
64 const categories = post.categories?.nodes?.map(category => category.name) ?? [];
65</script>
66<a href="/blog" class="blog-link">&#8592; Blog</a>
67<article>
68 {#if post.featuredImage}
69 <img src={post.featuredImage.node.sourceUrl} alt={post.featuredImage.node.altText} />
70 {/if}
71 <h1>{post.title}</h1>
72 <p class="post-meta">
73 ✍️ {post.author.node.name} on {formatDate(post.date)}
74 </p>
75 <div>{@html post.content}</div>
76 {#if categories.length}
77 <div class="category-list">
78 <h4>Categorized As</h4>
79 <p>{categories.join(', ')}</p>
80 </div>
81 {/if}
82</article>
83
84<style>
85 .blog-link {
86 text-decoration: none;
87 }
88 article {
89 margin-top: 2rem;
90 }
91 article img {
92 max-width: 100%;
93 }
94 .category-list {
95 border-top: 2px solid var(--color-gray-9);
96 margin-top: 2.5rem;
97 padding-top: 2rem;
98 }
99 .category-list h4 {
100 margin: 0;
101 }
102</style>

Test a Post page

Now that the dynamic routes are all set, we can click the individual post to see the detail contents:

local by flywheel configuration

Adding a Podcast Player

So far, we retrieved blog post data from wordpress graphql server and rendered in our SvelteKit front-end application. Because the SvelteKit is a SPA(Single Page Application), we can create a persistent component that can be rendered throught all the pages. For example, we can create a footer like component that plays a podcast, but you can still navigate to other pages without interrupting the playing podcast.

Create a PodcastPlayer component:

./src/components/PodcastPlayer.svelte
1<div>
2 <iframe
3 src="https://www.buzzsprout.com/1724287/9511712-gutenberg-with-jason-bahl?client_source=small_player&amp;iframe=true&amp;referrer=https%3A%2F%2Fwww.buzzsprout.com%2F1724287%2F9511712-gutenberg-with-jason-bahl.js%3Fcontainer_id%3Dbuzzsprout-player-9511712%26player%3Dsmall"
4 loading="lazy"
5 width="100%"
6 height="200"
7 frameborder="0"
8 scrolling="no"
9 title={`DE{CODE}, Gutenberg with Jason Bahl`}
10 ></iframe>
11</div>
12
13<style>
14 div {
15 position: fixed;
16 background-color: var(--color-white);
17 box-shadow: var(--box-shadow-2);
18 left: 0;
19 right: 0;
20 bottom: 0;
21 }
22</style>

Render this component in the layout svelte file.

./src/__layout.svelte
1<script context="module">
2 import "../layout.css";
3 import PodcastPlayer from "../components/PodcastPlayer.svelte";
4</script>
5
6<main>
7 <slot />
8</main>
9<PodcastPlayer />
10
11<style>
12 main {
13 padding: 2rem;
14 max-width: 800px;
15 margin: 0 auto 12rem;
16 }
17</style>

As you see, the podcast player appears in the bottom of every page that you navigate to.

local by flywheel configuration
all posts

Comments

© 2022 Youngjae Jay Lim. All Rights Reserved, Built with Gatsby