josh-collinsworth / joco-sveltekit

The home of my static SvelteKit site.
https://joshcollinsworth.com
70 stars 14 forks source link

blog/build-static-sveltekit-markdown-blog #9

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

Let's learn SvelteKit by building a static Markdown blog from scratch - Josh Collinsworth blog

Learn the fundamentals of SvelteKit by building a statically generated blog from scratch, with Markdown support, Sass, an API, and an RSS feed.

https://joshcollinsworth.com/blog/build-static-sveltekit-markdown-blog

prinpadure commented 2 years ago

For someone who starts learning SvelteKit, this tutorial is amazing!

SILVAWesley commented 2 years ago

Amazing job! Thank you, I was kinda lost with the amount of outdated SvelteKit tutorials, but this one suited well for me. Also, I like that it is well written.

sgeisenh commented 2 years ago

What's the advantage of using a JSON endpoint over creating a helper function that returns the post metadata?

josh-collinsworth commented 2 years ago

What's the advantage of using a JSON endpoint over creating a helper function that returns the post metadata?

Great question! If all you're planning on doing with the posts is listing them out on the blog index page, then a helper function would be just fine. I like having an API, though, because it enables you to do lots of other things down the road—for example, fetch posts for a sidebar or recent posts list, or even add a search feature to the site. Plus, you can use your content on other sites if you want to.

spoonfulofdreams commented 2 years ago

Under Finishing the Blog Index Page > Server-side Rendering with Load, I edited the blog/index.svelte page with the code provided but I'm told I can't use the keyword 'await' outside an async function. It's in the line: const response = await fetch('/api/posts.json')

What's gone wrong?

sgeisenh commented 2 years ago

Under Finishing the Blog Index Page > Server-side Rendering with Load, I edited the blog/index.svelte page with the code provided but I'm told I can't use the keyword 'await' outside an async function. It's in the line: const response = await fetch('/api/posts.json')

What's gone wrong?

The arrow function where that line appears must also be marked "async":

export const load = async ({ fetch }) => {
  const posts = await fetch('/api/posts.json')
  const allPosts = await posts.json()
josh-collinsworth commented 2 years ago

The arrow function where that line appears must also be marked "async":

Good eye, and thanks for the reply! I've just updated the code in the post.

pejato commented 2 years ago

Josh, this was a really great article. I started to make a personal blog a few months back with SvelteKit but couldn't stick with it. Most everything here just worked, which was great. Also, your website is beautiful 😎

One thing I'll add is that I did run across this error message when I tried using the page param for load. The error message is fairly self explanatory, of course.

`page` in `load` functions has been replaced by `url` and `params
josh-collinsworth commented 2 years ago

One thing I'll add is that I did run across this error message when I tried using the page param for load. The error message is fairly self explanatory, of course.

`page` in `load` functions has been replaced by `url` and `params

Ah, thanks for bringing that up! The hazards of blogging about a pre-1.0 technology 😅 I'll see about adding an update for that.

janzheng commented 2 years ago

This post is incredible!!

A note from the https://kit.svelte.dev/docs#routing-endpoints docs — fetch is now supported in endpoints, and everywhere!

freeform99 commented 2 years ago

Hi Josh,

thanks for the great tutorial!

I noticed, however, a white flicker when scrolling this page to the top/bottom by using the Pos1/End keys. It only happens in Firefox and only in Dark Mode. (I first noticed it in my computer's dev environment but then I also noticed it here.)

I use TailwindCSS (which I guess you're not using), and I found pages that have dark mode but don't flicker (for example, https://adityatelange.github.io/hugo-PaperMod/posts/papermod/papermod-features/).

Do you see it as well? Any idea what's going on?

Gunnar

josh-collinsworth commented 2 years ago

Do you see it as well? Any idea what's going on?

@freeform99 Looks like it's nothing to do with SvelteKit or Tailwind; looks like Firefox's rendering engine just allows the default background to seep through for a split second when scrolling tall pages very quickly.

My current implementation of dark mode only sets the background color on the #app element, which covers the whole html body. Apparently forcing Firefox to render that much content that fast just causes a blip where the html body background shows through. Easily fixable by moving the background color to the html/body tag (probably a good idea anyway, I just wasn't always setting classes that high in the DOM).

Wolsten commented 2 years ago

Great blog Josh, wish I had come across it a couple of months ago - looks like I have some refactoring to do :-)

ajbajbajb commented 2 years ago

Josh, God bless you, this is an excellent tutorial for understanding endpoints in Svelte. You do mention extending this API to use the resolver default.render to access the .md content. How would you do this?

Please point me in the right direction if possible! I'm trying to get around the dynamic component restriction of building static sites. 🙏

josh-collinsworth commented 2 years ago

You do mention extending this API to use the resolver default.render to access the .md content. How would you do this?

It's similar to how the endpoint grabs the metadata from the resolver, just with an extra step (since default is a reserved keyword in JavaScript):

const post = await resolver()
const content = post.default.render()

If you run that inside the endpoint code, alongside where we get the metadata and the postPath, then you should have the HTML output of the post's content in the content variable.

ajbajbajb commented 2 years ago

You are an absolute star. That's it. Such an unbelievable help.

codyburleson commented 2 years ago

This is one of the best tech tutorials I've ever completed - it is clear, concise, error-free, and very well illustrated. Thank you!

mosherbrian commented 2 years ago

After feeling very lost trying to get started with svelte-kit, I stumbled across your excellent tutorial, and now I see the light. Thanks so much for your clear and thorough explanations. I feel like this should be a required tutorial for anyone starting out with svelte-kit. I'm definitely buying you a coffee!

bnonn commented 2 years ago

Josh, thanks for writing this, it is very helpful. I have been investigating SvelteKit for creating an app, but have been pondering whether to also adopt it for an SSG. I prefer to not use a bunch of different tools if one will cover all the use-cases...

However, reading this, it seems like SvelteKit adds a lot of extra work compared to something like 11ty or Astro. (I'm very familiar with 11ty, but looking at Astro for its ability to easily integrate Svelte components.) There's a lot of boilerplate required to do something like set up and retrieve a collection, for instance. Am I getting an accurate picture, or am I missing something?

josh-collinsworth commented 2 years ago

@bnonn I think your picture is accurate. As mentioned in the intro, SvelteKit definitely isn't the simplest thing you could use for a static site generator. It's more powerful and free-form, which also makes it more complex. 11ty and Astro are both great choices for pure static sites.

JappIC commented 2 years ago

Hello. Thank you very much for this great tutorial. A few months ago I made one of WebJeda and now I update and optimize it following yours.

Everything works fine and now my code looks much more readable.

I just wanted to tell you about a problem that is happening to me after I updated SvelteKit yesterday. When you interact with the web from the links everything works fine, but for some reason if I copy the link from some post... and paste it into a new browser window, the url throws a 404 error.

The application is compiled as static and hosted on an apache server, in previous versions it worked.

Again thank you very much for the tutorial.

koenraijer commented 2 years ago

@ajbajbajb I get part of the object with html visible in there if I console.log it, but then it crashes halfway through with the error: Cannot read properties of undefined (reading 'data').

I suspect there's something in the .md files that breaks it, maybe because inside that .md file I fetch an endpoint? Anyone has an idea?

josh-collinsworth commented 2 years ago

I just wanted to tell you about a problem that is happening to me after I updated SvelteKit yesterday. When you interact with the web from the links everything works fine, but for some reason if I copy the link from some post... and paste it into a new browser window, the url throws a 404 error.

Sorry, @JappIC, I'm not able to replicate this. Maybe it's a version issue? If you run npm update, does it still happen? And if so, if you could link to a repo where the issue can be seen, I'll take a look.

JappIC commented 2 years ago

Lo siento, @JappIC , no puedo replicar esto. ¿Quizás es un problema de versión? Si corres npm update, ¿todavía sucede? Y si es así, si pudiera vincular a un repositorio donde se puede ver el problema, le echaré un vistazo.

Yes, I updated to the latest version of Svelte and added...:

prerender: { default: true, }, ...as requested now.

I realized that if I add .html at the end of the url it works. I sent you access to my repository

Thank you very much for your attention

josh-collinsworth commented 2 years ago

@JappIC I'll take a look. One thought: this may be an issue with the host's web server configuration. Where is it hosted currently?

On Netlify, Vercel, etc., a route like /post-title should automatically fall back and load the /post-title.html route behind the scenes. Maybe this host is not set up to do that?

PS the site looks great! 👏

[EDIT]: I also wonder if maybe the deploy configuration mentioned in this issue might help.

JappIC commented 2 years ago

PS the site looks great! 👏

Thank you very much!!!

It's an apache server "banahosting", that's why I want it to be static. I also have my website hosted with an older version of svelte and it works. However, the application to which I gave access is updated to the latest version of svelte and generate this problem.

The 404 error is generated by the server, not the application.

I don't think the problem comes from what I learned from you in this post, which helped me a lot to improve the code. I think it's something from svete

sebbasim commented 2 years ago

I can't figure out how you're dynamically getting a single post, like here:

    const post = await import(`../../../lib/posts/${params.post}.md`);

Sveltekit is complaining about SSR / not being able to dynamically import a module. Alternatively, usingimport.meta.glob can't handle variables inside the string literal.

I resorted to just getting all the posts and then filter out the single post. Are there some additional settings to allow for the code in [post].svelte to work?

3CordGuy commented 2 years ago

Great write up, Josh! Big SvelteKit fan myself and was currently looking for some supplemental examples for how folks like to handle markdown with SvelteKit and you didn’t disappoint. Well done and thanks for all the effort!

mateoroldos commented 2 years ago

OMG this post is all I needed to start my SvelteKit + MD project.. thank you so much Josh!

I have only one question.. do you have any experience of using this stack + internationalization???

stephang commented 2 years ago

This is really a great article! As SvelteKit's official docs are still somewhat lean, your article is very helpful. Thanks a lot!

One question, though: I wonder if in my adapter-static-configured project, if I can have routes built with Vite. In your example it's the routes for /api/posts.json and /rss.xml.

To reproduce my use case:

If I run npm run dev, I can open the route and get some nice RSS data.

Did you ever have this working on a statically built site? Any idea if this is supported by SvelteKit?

josh-collinsworth commented 2 years ago

@stephang It's been a while, but I do remember having this issue, too. IIRC, it's one or both of the following:

  1. The SvelteKit router tries to render the page on first load;
  2. SvelteKit doesn't pre-render API routes unless they're actually requested somewhere in the application.

I have this line inside a load function somewhere in my site, just to make sure the RSS feed renders:

const rss = await fetch('/api/rss.xml') // This isn't used; it's just here to make sure the route gets prerendered

I wanna say there's a more elegant way to manually add prerendered routes, but I ran into issues that way, so I just went with this little hack.

I also made sure to add rel="external" to any link on the site that goes to the RSS feed, to make sure the router doesn't get in the way.

stephang commented 2 years ago

@josh-collinsworth That works! Thanks for the hint! 🙏

Following your implementation, here's how the fix looks in my project:

// In /routes/__layout.svelte
<script context="module">
    export const load = async ({ fetch }) => {
        // This is a hack to make sure the route gets prerendered.
        const posts = await fetch('/api/posts.json')
        return {}
    }
</script>

// Rest of the layout file follows below.
git-no commented 2 years ago

It was a pleasure to read this article, very helpful, specific, easy to read. Thank you very much.

lyan-ap commented 1 year ago

great work

janzheng commented 1 year ago

For anyone running into trouble, this post helped me: https://stackoverflow.com/questions/71242485/how-do-i-write-an-async-request-to-get-a-markdown-files-content-svelte where they recommend installing vite-plugin-markdown

I added this line to vite.config.js: plugins: [sveltekit(), markdown({ mode: Mode.HTML })],

and my posts.json.js file now looks like:

// posts.json.js
export const GET = async () => {
  const allPostFiles = import.meta.glob('../blog/*.md')
  const iterablePostFiles = Object.entries(allPostFiles)

  const allPosts = await Promise.all(
    iterablePostFiles.map(async ([path, resolver]) => {
      const { attributes, html } = await resolver()
      const postPath = path.slice(2, -3)

      return {
        meta: attributes,
        path: postPath,
        content: html,
      }
    })
  )

  const sortedPosts = allPosts.sort((a, b) => {
    return new Date(b.meta.date) - new Date(a.meta.date)
  })

  return {
    body: sortedPosts
  }
}
emcog commented 1 year ago

@janzheng thanks for the link. Did you run into any problems routing markdown files?

I currently get 404s whenever I route to a md, however my api and corresponding index is reading/rendering the content of the markdowns correctly

janzheng commented 1 year ago

@janzheng thanks for the link. Did you run into any problems routing markdown files?

I currently get 404s whenever I route to a md, however my api and corresponding index is reading/rendering the content of the markdowns correctly

Yeah you're right, I'm having some trouble with preprocessing Markdown. Switched to mdsvex and it works.

It's much more complicated though. Here's what I did:

  1. I added this under svelte.config.js:

    import { mdsvex } from 'mdsvex'
    ...
    preprocess: [
    ...
    mdsvex({
      extensions: ['.svx'],
      // optional; I got these from a tutorial and it's super useful; these work like sveltekit layouts
      layout: {
         content: 'src/lib/layouts/_content.svelte'
      },
      remarkPlugins: [
        remarkAttr,
      ],
      rehypePlugins: [
        rehypeSlug,
      ]
    }),
    preprocess({
      postcss: true,
    }),
    ]
  2. Then I added a markdown.svx file under routes and it works:

    
    ---
    layout: content
    ---

Markdown content here

Here is a Svexed thing:

More markdown here!

gerhardcit commented 1 year ago

Really great covering all the topics. Thx. I’ll point any newbie to this article.

Have you being able to display a mermaid diagram in the markdown files yet? That would be super usefull.

emcog commented 1 year ago

Hi @josh-collinsworth, thanks for the excellent tutorial and fleshed out repo, which I am working with.

I have created a component that lists the titles of each post. I did this by sending posts to a store then looping the through each post.title. While this seemed a bit verbose it was simple and worked.

Now, I am trying to build component that will list out the unique categories.

I originally planned on using the posts store, then arranging/tidying the data into a set.

... edit:

The solution is to arrange the data in route which receives the data from the api rather than a component. e.g. "routes/posts/index"

Thanks again

nusendra commented 1 year ago

incredible post josh !!!

emcog commented 1 year ago

Following my previous comment, I have run into a weird bug. Which I guess is to do with lifecycle. How would I go about tracking/debugging the below? Thanks in advance.

Edit: I have not updated to the latest version of Svelte just released

I have a SecondaryNav component which renders: • a list of unique categories and • a list of titles of all blog posts.

The user can: • click on a category and it goes to a list of all posts which share this category or, • they can click on a title and visit it's article

When I load /blog both the lists render and function as expected. But when I load /blog/category/anycategory or /work/anypost they don't work. Categories don't render at all and titles render undefined.

When I open a new tab and load /blog/category/anycategory or /work/anypost the list show's briefly but then updates so that there are no categories and titles render undefined.

The design for this feature is to my mind at least, reasonably straight forward:

routes/blog/index: arrange data and save to a store lib/assets/components/SecondaryNav: import store and create two lists

EDIT requires an if around the __each___ {#if $variableAttachedToStoreOnMount} {each $variableAttachedToStoreOnMount as e} <li> ... </li> {/}{/} when the reactive declaration $ is removed from the {#if} while the server is running all the categories and titles list on every page. But when the server is started without the $ the site fails to load as Cannot read properties of **undefined (reading 'length'**)

`routes/__layout: import and implement SecondaryNav```

Below is more detail for blog/index and SecondaryNav

routes/blog/index 

//save posts to store
storePosts.set(posts)

//arrange categories into unique entries and save to a store
posts.map(e => arraysOfCategories.push(e.categories)) 
                //"posts might not have been initialized"
        duplicateCategories = arraysOfCategories.flat(2)
        duplicateCategories.forEach(e => setCategories.add(e))
        uniqueCategories = [...setCategories]
        uniqueCategories.sort()
        storeUniqueCategories.set(uniqueCategories)

                console.log('store uniques', $storeUniqueCategories);
               // prints out as expected

lib/assets/components/SecondaryNav
// import posts and categories, and wrap in <li> tags

<script>
import { storePosts, storeUniqueCategories } from "$lib/assets/js/store";
    import { onMount} from 'svelte';

    let navPosts;
    let navUniqueCats;
    onMount(navUniqueCats = storeUniqueCategories)
    onMount(navPosts = storePosts)

    onMount(()=> console.log('secondaryNav', navUniqueCats, navPosts)) 
       // prints Undefined, Undefined
</script>

<nav>
  <ul>
    <li class="categories">
        <p>Categories</p>
        <ul>
        {#if $navUniqueCats}
          /* needed if tag because console returned a .length error when starting the server, i.e. it would work without if tags if the server was running and then code written/uncommented.*/
          {#each $navUniqueCats as navCat}
            <li><a href="/work/category/{navCat}">{navCat}</a></li>
          {/each}
        {/if}
      </ul>
    </li>
    ... repeat for posts
</nav>```

Thanks for getting this far :)
emcog commented 1 year ago

Update: I'm almost certain this is to do with the sequence in which components are loaded.

From the console:

Screenshot 2022-08-23 at 17 12 32

Question, what would the best way of managing this be?

Edit – I'm planning on refactoring and making use of +layout specific for posts and post files

janzheng commented 1 year ago

Thanks for updating your post to reflect the new Sveltekit changes!!

I'm curious, does doing a fetch const response = await fetch('/api/posts') in +page.js, when using adapter-node cause a round-trip?

Does anyone know if it's slightly faster to directly get the posts with fetchMarkdownPosts() within +page.js instead of doing a fetch()? Do we know if it calls the API endpoint as an external fetch, or if it's smart enough to treat it like an internal method?

josh-collinsworth commented 1 year ago

@janzheng My understanding is that, when using the fetch argument inside of load, fetch comes with a lot of extra niceties—one of which being the intelligence you're describing, and another being the ability to fetch relative routes server-side. See fetch in the loading data docs

git-no commented 1 year ago

I am wondering what is the better dynamic routing approach #2 or #3. Static site generate would be from performance perspective the goal.

2 sub directories seems not to supported by import(`../${params.slug}.md`);

josh-collinsworth commented 1 year ago

I don't know of any performance difference between the two options, or any reason an import wouldn't work with either one.

timganter commented 1 year ago

Thank you for this amazing tutorial! It's super helpful! 🎉

I think that since the sveltekit breaking changes update, the modifying the svelte.config step may not be necessary? It's already there in my svelte.config.js.

But I dunno if that's correct or not. 🤷🏻‍♂️ That's why I'm reading your blog post! 🙂

This is what my svelte.config.js looks like before editing it...

import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
    // Consult https://github.com/sveltejs/svelte-preprocess
    // for more information about preprocessors
    preprocess: preprocess(),

    kit: {
        adapter: adapter()
    }
};

export default config;
timganter commented 1 year ago

Looks like also maybe Prerendering with static adapter changed too? Seems like maybe it's not set in svelte.config.js but within the desired .svelte file? See this PR https://github.com/sveltejs/kit/pull/6197

josh-collinsworth commented 1 year ago

@timganter Did you choose TypeScript when setting up the project? If so, that may be why preprocess was already in place.

Also, just made an update on setting the prerender option properly. Thanks for pointing it out.

timganter commented 1 year ago

Yes I did choose TypeScript! I guess that was the reason huh? Thanks again! 🕺🏻