Convert Markdown & CMS Content Into Type-Safe JSON
with Johannes Schickling
Contentlayer is an SDK that transforms content from any source (including Markdown) into type-safe JSON. Johannes Schickling will teach us how it works and how we can use it in our own projects.
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show, we're bringing back Johannes Schickling. How are you doing, man?
JOHANNES: Hi. I'm doing great. Yeah, it's been forever, I think, since we had the pleasure last time together. But I'm very stoked to be showing you some of the stuff I've been working on recently. And there's certainly a few parallels that we'll dive in. So I'm excited.
JASON: Yeah. Yeah, it's always a pleasure to have you on the show. You're always working on something cool. And I guess before we dive into what you're doing now, let's get a little background. So for folx who aren't familiar with your work, do you want to kind of bring us up to date on who you are, what you do?
JOHANNES: That's what I've been doing for quite a while now. And I've went back to building projects, building a little products, and wanted to learn as much as possible of, like, what has changed in the last, I mean, it's now, like, having started Prisma was more than six years ago and a lot of things have changed since then. We've been always building products and tools for developers. But I'm kind of, like, envying the developers who can use them and I couldn't really use them myself, so now I can again. While using them, I've also experienced other friction points when building products and using some tools. And especially when building websites and what's important about websites is, like, that they try to communicate some information that's typically in the form of content. As opposed to applications dealing a lot of with data. But, like, one form of data is content. A specific form of data. And turns out there is also a lot of friction around working with content, especially in combination with more modern site frameworks such as Next where you have to do a surprising amount of rolling content pipelines yourself.
JOHANNES: While building a website, I went down a rabbit hole and tried to improve my own workflows and ended up building a little tool that turned out to be useful for others as well and eventually became what was released a few weeks ago as Contentlayer. And, yeah, so that's one of the things I've been working on over the last while. And what I want to, like, show you today and help you get to use it and build a little thing with it. So I think that would be fun for today's session.
JASON: Yeah, I think it's gonna be a blast. And so to kind of get, like, our heads around why Contentlayer exists, I feel like you did a good lead‑up here. I just ‑‑ I want of ‑‑ I want to share something that's been frustrating for me lately. Which is I feel like one of the things I love the most when I'm working as a developer is the, like, the docs code workflow or content as code. I like to write blog posts in Markdown or MDX and, you know, the ability to just kind of commit a blog post, I push that to GitHub, and then I know it's gonna go live on my site, is wonderful. But as you said, especially with things like Markdown, there is a surprising amount of having to roll your own pipeline to make that work. Because, you know, when you use a CMS like Sanity or Contentful or Strapi, any of these, right? Headless WordPress, they're so good at this content input. The content management, right? You log into a UI. It's a beautiful UI. You make changes. Commit those changes. I've got an API end point. I pull the content in. All wonderful. But if I'm working on a docs site, I can't give everybody access to this CMS. Like we want community contributions. We want people to be able to weigh in and give us, you know, a small typo fixes or things like that. They're like, oh, if I could, I would fix it really quickly, but instead, I have to DM somebody and have got to put it on a roadmap and figure out when they can find time to go into the CMS to make this typo fix. So there is this efficiency thing with working directly in the code with things like Markdown and things like that. But I have such a different relationship developing with Markdown. Like I hate it. I hate developing with Markdown or with MDX. I love writing in it. I hate working with it. It creates this tension because I don't necessarily want everything I do to be running on a CMS, but if I want to build quickly, I absolutely want to use a CMS for the building of the site. Because I like this idea of I get content. And that content is an object. And I can just use it. And there's, you know, there's API end points for getting all the stuff I need. Whereas with Markdown I'm like, okay, now I got to figure out what library is actually gonna let me load these files and then parse them. Okay, what if I need to do transformation ‑‑ I just did this yesterday. I had the Learn With Jason site is build in Remix, and they have a pipeline that I had to copy/paste the code to load the MDX files, but then I wanted to transform the metadata so that I could, like, generate social images. So I had to build this utility file that would, like, read the MDX file into memory and then parse all this stuff. I was like, man, this is a lot of work. This would be a lot easier if I was using a Content Management System. I hate writing them. I want to write my blog posts as code. So this is the pain I've been feeling. One of the reasons I got so excited when you started talking about Contentlayer is that it sounds like the promise of this from what I'm reading on the site is that you found a way to give me that experience of, like, my Markdown is effectively an end point. I'm pulling it in. I have access to my front matter. I have access to all the data as if I was loading it from API, right?
JASON: But I can write it as code. Is that correct?
JOHANNES: Exactly. And there is, like, so many little threads that you've, like, spread throughout, like, your ‑‑ the last few minutes that I would love to pull on. Since even with more polished experiences, let's say, Sanity or Contentful, et cetera, while the editing experience ‑‑ for the content editing experience is obviously worth a lot. The experience for a developer actually integrating it, there is also, like, tons of little, like, paper cuts that you hurt yourself with.
JOHANNES: But then when you change your content and you want to see it, like, live in your page, it's not just, like, any page. You don't just hand out Google Docs to people, but you want to make it look and feel like your blog, your documentation, your whatever.
JOHANNES: So you want to see it in that context. Then you need to, like, reload the page. That triggers, like, all the server‑side rendering. It kills sort of the feedback ‑‑ the feedback cycle. And that's something I value very highly, like, getting into the flow of, like, content writing. Getting into the flow of development. And it's all about the feedback cycle. It should be all, like, very smooth and very, like, you should just be in that flow. And I try to see, like, whatever are these little things that could just make it a delight to use, and lets you focus on, like, building your web experience, writing that content. That's sort of, like, the influencing theme of Contentlayer. And it really, the devil's in the detail. And there's many little devils depending on, like, where the content is coming from. And so there are various variations. And I think today we'll mostly take a look at, like, Markdown or MDX. MDX is sort of, like, a wild beast and has kind of been un‑defined anyway. There is now a like version 2, which is great. But there is still a lot of open questions generally around, like, how to work with MDX. So Contentlayer has its own take that tries to incorporate as many best practices that sort of merged over time.
JOHANNES: But, yeah, let's dig into that as we go.
JASON: Yeah. And so before I flip over to code, just a couple more questions around this. Because I think this is always fascinating, right? So you mentioned you were one of the Co‑Founders of Prisma, and we've talked about Prisma on the show before. I can share a couple links for folx so that we have those available. But we, like, one of the things that is interesting about Prisma and one of the reasons that I think we're starting to see Prisma show up in so many stacks is that it takes this idea of data access and removes the need for me as a developer to know the difference between accessing MySQL versus any other flavor of database, things like that, and gives me the ability to have kind of one unified way of thinking about databases. I write the Prisma schema. I use Prisma's migrations. And I get TypeScript auto complete. I get, you know, the guarantee that things work the way they are. You have kind of a standardized API. That feels really nice to use. If you, you know, for y'all in the chat who have tried this. If you go and build with a site that's a stack using Prisma and it uses MySQL and you decide you want to spin up Prisma instead or, like ‑‑ sorry, you want find one that is using PostgreSQL. Swapping it out is, like, one config file instead of having to back it out of every single file that's importing the MySQL connecter or the PostgreSQL connecter. It really does create this nice abstraction when we're working with data. Did y'all land on, like ‑‑ is Prisma an ORM or is it something else?
JOHANNES: [ Chuckling ] so I think that's mostly nomenclature. So there is, factually, there is a definition of, like, that some people came up with what an ORM is. And by that definition, we've tried to be very correct early on. And by that definition, Prisma is kind of not an ORM. But eventually we just kind of gave in and saw that people think about it like that. And let's just not be, like, too factually correct, but let's just ‑‑ if people think about it, like, in that box, it's still, like, the biggest worry for us has been that ORMs has a really bad rap.
JOHANNES: Because there is just arguably a lot of, like, building one that is really great is really hard. And most previous attempts have just stopped halfway there. And we've been also a bit worried about, like, that people would just not take it serious if we would use that term. But I think by now, we've really got the trust of developers and they see, like, holy moly, I didn't ‑‑ I didn't like ORMs before, but this is something I'm actually using in my production app. So, yeah, to bring it back to Contentlayer, it's kind of funny, because a lot of the similar ‑‑ I've talked about it, like, broadly from, like, trying to come up with a great developer experience and solve these friction points. And Contentlayer, you can kind of think about it as like Prisma but for content. So there is a lot of similarities. And, yeah, also just as a disclaimer, this is really just, like, a project I'm working on. There is no intent to build a company based on Contentlayer, similar to Prisma. So... this is really something that solves my own pain points and I thought it was ‑‑ would be useful to add this as well. Yeah, eventually, like, I ended up building ‑‑ as I worked on this project, it turned out there was a company I'm actually advising called Stack Bid and turned out that this would be actually a useful thing for them that's strategically relevant to their users.
JOHANNES: So they ended up really materially sponsoring that work. And that allowed me also to spend a bit more time on it.
JASON: Cool. That's extremely cool. And yeah, so when you take the ‑‑ all the learning that you've had from Prisma, and we know that the thing that makes Prisma uniquely valuable is its normalization over working with databases, which traditionally, you know, you had to be a database expert. Not expert, but you had to have more knowledge than you maybe wanted to to be able to work with a given database.
JASON: Prisma's made it so a front end developer with enough knowledge to be able to spin up a database can now work with any database. Like you ‑‑
JOHANNES: You don't even need the knowledge to spin it up. You might need to go to plan and scale and clickitty clack and now you have a connection point and what you need.
JASON: That's a really good point. We're really flattening the playing field. I do think tools like Prisma do this power‑up, I would say ‑‑ we saw a lot of talk in the past about this idea of a full stack developer. I think people who have been developing for a long time, we all kind of brush that off as hyperbolic. We say there is no such thing as a full stack developer. Nobody can learn all of this stuff. You can be a little bit good at all of these things, but you'll never be a deep expert, right? And what I've noticed is with tools like Prisma and, you know, tools like Netlify, tools that basically abstract over the pieces of the backend that are commoditizable. A solid front end developer with a little bit of Node knowledge can accomplish what a full stack developer would claim.
JASON: Because they can build full apps. You can build really hardcore, full apps that will scale to whatever, that are runable on virtually any infrastructure. It's really incredible how much you can get done as a single developer with tools like Prisma. So how much of that is, you know, I know you're saying that Contentlayer's not intended to be a company. So it's not gonna get the same level of investment that Prisma got. But when you're talking about, like, is this just a logical next step? Where if Prisma is about low‑level database access, Contentlayer is about, you know, all the things that get built on top of databases and need normalization again?
JOHANNES: So I wouldn't say it's, like, this is, like, clearly step 1, this is step 2.
JOHANNES: I would rather say that this was just, like, work that hasn't been done yet in the ecosystem. And everyone kept, like, rolling their own thing. I would say Contentlayer in itself, even though it can be and should be expanded way more broadly to different content sources, et cetera, I think the magnitude of, like, a problem that it's trying to solve is quite more manageable and quite a bit smaller than, like, Prisma is really, like, a monumental effort. And even though it has seen now years of, like, really solid engineering and development, there is still, like, we are kind of, like, just getting started with all of the things that Prisma wants to solve.
JOHANNES: So I think these are really, like, two domains. Maybe you want to build an app and deal with data, so you're using Prisma. Maybe you build a site deal with content. This is where maybe Contentlayer can be helpful. So I think it's mostly been I couldn't help it. And I couldn't leave that problem to be unsolved. So I applied my previous wisdom, maybe wisdom, to, like, have a stab at that. And, I mean, you personally I think also have a very interesting relationship given your history at Gatsby since I think Gatsby that pioneered this path really, really well and has done such a great job of, like, really figuring out some of these development patterns, et cetera. And I think it's a fantastic tool for, like, building out sites that especially combine, like, different sources of content, et cetera.
JOHANNES: For my taste, Gatsby has always felt a little bit more like a big cannon for, like, smaller things. And so I typically would rather like to see how far could I get with something like Next.js or something more nimble. But Next.js, for example, you really have to do all of this yourself.
JOHANNES: This is where I basically tried to have a mini version of what are the the great parts about Gatsby, make that standalone available, and that's what Contentlayer is.
JASON: Yeah, okay. So, that's a good ‑‑ I think a great overview of how it works and probably from here, anything else I ask would just be easier to show. So let me switch us over to the Pair Programming view. And I'm gonna probably have to fix this real quick. I am. Give me just a second. Because I broke all sorts of things. Let me get this ‑‑
JOHANNES: In the meanwhile, I can also, like, for everyone interested, feel free to also check out the Contentlayer website. We've put that online a few weeks ago. And we've really tried with that website to make it as educational as possible. And also, like, very interactive. So that you can, like, get the ideas how it's working. And we've actually, if you're on Chrome or in firefox, you can have a real‑life project that uses it embedded in the website itself to kind of get a feel for it. Kind of inception where you have IDE in your website. You can try changing content there or changing a bit of the Next app. You see in realtime refresh. If you're lazy, myself included, to really create a new project, set everything up, and you just want to give it a try on the website, you can do so right away. But I think today we'll get a bit more hands on. But this could still be a good starting point.
JASON: That's great. Okay. Cool. So before we get started with the code, let me do a quick shout‑out to our captioning. We've got Jordan here with us today doing the live captioning from White Coat Captioning. Thank you, Jordan, for being here. That is made possible through the support of our sponsors. We've got Netlify, Nx, and Backlight all kicking in to make the show more accessible. We are talking to @JohannesSchickling on Twitter. Make sure you give a follow.
JOHANNES: Thank you so much.
JASON: Talking about Contentlayer. There it is. All right. We've talked about why it exists. We've talked a little bit about what it's for. Let's talk about how we should get started. What should I do if I want to build my first Contentlayer site?
JOHANNES: So Contentlayer is not sort of, like, the main thing. Contentlayer fits into, like, the thing you want to build. So it's a piece of that. In the same way Tailwind is not the same thing, but fits into your Next app, Remix app, et cetera. You think about it similar to that. So I would suggest for now we'll set up a Next.js project and try to use Contentlayer in that. Contentlayer, in theory, is agnostic to the site framework you're using or to the content sources that you might want to use. Right now, Contentlayer works best and has, like, a first‑class integration with Next, but in the future, there will also be other integrations and more content sources. So for today, I would suggest we use Next and we use, like, some content, like, you've motivated initially with Markdown, et cetera, and try to build a little website there. Yeah, I think this could ‑‑ you could use this for, like, your website. Your blog. Your documentation. It's really flexible.
JASON: Thanks. Okay. So I'm gonna npx create‑next‑app@latest, and we're gonna call this nextjs‑contentlayer‑docs. Are we gonna use MDX today?
JOHANNES: Yeah, we could even mix and match. One decision we might want to make is whether we want to use TypeScript here or not. TypeScript is optional, but it's still, I think, like, it unlocks, like, a nice aspect of it. But that's kind of up to you, whether you already see yourself as, like, using TypeScript by default or not yet.
JASON: So I am happy to use it. Are they ‑‑ does it not ‑‑ I thought it would ask me.
JOHANNES: For the TypeScript part?
JOHANNES: Maybe you would need to ‑‑ maybe you would need to do ‑‑
JASON: Let me see if it ‑‑
JOHANNES: A flag or something.
JASON: Did it set up TypeScript? They didn't.
JOHANNES: But, I mean, we can also manually switch it, but maybe ‑‑
JASON: We'll just ‑‑
JOHANNES: Maybe look in the docs whether there is, like, a flag for that.
JASON: We'll remove that folder and we'll try again with ‑‑
JOHANNES: There you go.
JASON: Ah, here it is. There is a TypeScript flag. I thought it was gonna ask me. Okay. So we'll go here, here, and then we're gonna ‑‑
JOHANNES: Again, optional. Works basically the same way both directions. But I think it sort of, like, demonstrates some of the even cooler parts, in my opinion. But that's really up to you.
JASON: Great. All right. So let me ‑‑ I think ‑‑ let me open this up. And now we've got a running site that I need to git init. I'm gonna close this and open it again so that we don't have everything dimmed out on us. So now everything is TypeScript. So we have a basic site. And I think we can just kind of declare bankruptcy and start all over again. So let me ‑‑
JOHANNES: That sounds great.
JASON: ‑‑ get rid of this. We'll do, like, Contentlayer + Next.js. Maybe just skip a description altogether. And this main we'll empty out entirely and figure out what we're gonna do next.
JOHANNES: Right. Cool.
JASON: Take this out. Footer. And we are back on a just standard, nothing special, it's just a basic Next app. So I can run npm run dev, and that will set it up for us. It set it up with Yarn. That's fine. It fixed the ‑‑
JOHANNES: These tools have gotten quite good at, like, accepting each other's existence.
JASON: All right. So ‑‑ oh, it's running. Let me go fix that. So now we can come out here and host 3,000.
JOHANNES: Hello chat.
JASON: Here is our very basic "hello chat." So what we wanted to do was set up like a basic docs site, right? So if I ‑‑ is there a special way to set this up? If it was me, what I would do is put together, like, a content folder and start putting stuff in it.
JOHANNES: Perfect. Let's do that. Let's do that.
JASON: Okay. So I'm gonna create a folder for content. Inside of it, I'm gonna create a new file called like index.mdx, and then maybe we'll have another folder for, like, getting started and inside of that we'll have step 1.mdx and maybe a step 2, but this one didn't need any special components so it's just gonna be a regular Markdown file. And then inside of here, I would have, like, my title and it would be, you know, welcome to SuperTool. And SuperTool is gonna be, I don't know, like, SuperTool is the next big thing in devtools.
JOHANNES: I was actually curious like what GitHub Co‑pilot would have suggested what is the next big thing.
JASON: That's a great question. Let's find out. The SuperTool ‑‑
JOHANNES: I think this is a winner, no?
JASON: We've got X for Y and we need to create some components. Which we'll do momentarily. For now, let's go over here and say, step 1, get started.
JOHANNES: I think the entire developer relations industry is coming to a close now that we have Co‑pilot and can write documentation.
JASON: That's right. I should just turn it off. Stop doing my job. [ Laughter ] We'll do, like, a shell and then, like, an npm install.
JASON: All right. So that will be our step 1. Then we can go to our step 2. I have a step 2.
JOHANNES: There is actually a thing on NPM called SuperTool.
JASON: Does it really?
JOHANNES: It has very few downloads and published five years ago. The more you know. This is our setup that, like, based on your intuition, this is what you want to have. But now it's a question of, like, how do you make it work? Like how ‑‑ go back to the browser and you want like the hello chat to display your content.
JOHANNES: Ideally, you want to have some stylistic control how you put it in a component, et cetera. So this is ‑‑ that's an excellent starting point of, like, hey, how do we make this work?
JASON: Mm‑hmm. So we've got our basic docs here. And if I was gonna do this now, I would start by googling how to load MDX in Next.js. Which would lead me to probably somebody's built a package for this by now that would let me, you know, maybe import them or maybe I could put MDX in my pages directory. Or maybe I have to, you know, get, like, import, like, readfile sync from FS. And then in my, you know, export, and getStaticProps. I would need to, like, you know, await, readFileSync and then I'll have to go get my ‑‑ we'll kind of force this here. Then I have to do something like this. Then I have to, like, parse it.
JOHANNES: Exactly. Do all the things. And then, like, you might have, like, messed ‑‑ like, for example, the way ‑‑ how you've written line 6, would already make sure it's not working on Windows.
JOHANNES: So there is, like, a lot of little things that is very easy to, like, mess up. And, yeah, these are all of, like, the things that you shouldn't really have, like, what you've done before, that's actually the meaning. Coming up with an idea. I want to have a content folder. I want to have its format, et cetera. This is the stuff that is worthwhile thinking about and spending your creative energy on. But now all of that stuff is really, like, not unique to you. Everyone else has to figure this out. And you really shouldn't.
JOHANNES: So that's a perfect starting point for bringing in Contentlayer. So I think it depends on, like, now how you want to go about it. There is a first version of a getting started tutorial. I can also be your tutorial.
JASON: Let's have you be the tutorial. My favorite is to completely ignore the docs and just rely on you completely.
JASON: Let's show everyone where the docs are so that they can find them if they don't have you looking over their shoulder.
JOHANNES: And fun fact. Oh, I'm sorry. The Contentlayer website, including the docs, is also built with Contentlayer. If we're lazy and we don't want to do all of this by hand, by could also look at the Contentlayer website repo and see a more grown‑up example. So we have some really cool things in there. So for example, you can even do command "K" for, like, a search so you can write to your Next.js, for example. Then you can jump there to an example. This is all built itself with contentlayer Contentlayer. This is one of these examples that doesn't work in all browsers. If you want to later explore a more sophisticated website, the Contentlayer website itself and docs is built with Contentlayer, obviously.
JASON: Okay. So back to our hello chat here. So I'm assuming the first thing I need to do is install Contentlayer.
JOHANNES: Exactly. It would be npm install Contentlayer. Given that we're using Next.js, while it's not technically needed but provides an even better developer experience with Next. We also want to install Contentlayer.
JOHANNES: So both. Next, and Next‑Contentlayer.
JOHANNES: I'm not sure whether it's just on my screen. I can't quite see the bottom of your terminal due to the overlay. I'm not sure whether it's the same for others as well.
JASON: Whoops, I squished it, everyone. I wanted to fix this for the aspect ratio and forgot that I have some of the screen cut off. So let me fix that. All right. Here is the screen. This is the command I ran, was down here, npm install contentlayer and next‑contentlayer. And we have installed those.
JOHANNES: Awesome. There's a few next steps we got to do, in no particular order. So the first one I would suggest is creating a new file for that basically teaches Contentlayer how to do things. That's Contentlayer.config.js or ts. For now, let's stick with TypeScript.
JASON: This goes in the route, right?
JOHANNES: You can also configure it, but here, that's good here. Given we're somewhat lazy, and that's at least how I do it. Like I rarely write everything just manually keystroke myself. But I typically copy/paste and then adjust. That's what I would suggest now as well. So maybe we can jump over to the docs. And, yeah, getting started. And scroll down a bit. Yep. Yep. Scroll down a bit. Since we're doing it out of order.
JASON: JS config.
JOHANNES: This is now the one we're interested in.
JASON: Got it. Okay. So let's grab this and then we'll look at what it does.
JOHANNES: All right.
JASON: So here's a config. And if we start at the top, I can see that we are pulling in define document type and maybeSource.
JOHANNES: Maybe we can hide the posts so far and just ‑‑ yeah. So this is basically the entry point.
JASON: Okay. So we have a contentDirPath. Now I'm gonna make a guess here that that is gonna be content.
JOHANNES: And that is really, like, the ‑‑ the important part about the contentDirPath doesn't matter quite yet here. Let's say you have a more sophisticated setup. You have blog posts, you have documentation, you have, like, all sorts of different things, then you might want to put them all into content or different hierarchy. And DirPath is basically the route for that. Since often you want to use the file path, like, for example, here that is called step‑1. Maybe you want to translate that to a URL later so this is nicely relative to something. This is also what it's used for.
JASON: Love that. Okay. So then we have our document types. Which right now we have a post set up. So I'm gonna rename that. Oops, maybe not. Maybe I just want it to be this one and this one for now. We'll rename it to be ‑‑ we'll call it page, because there are gonna be docs pages.
JOHANNES: Sounds good.
JASON: Okay. So I can make a couple immediate assumptions, that my name is gonna match, and then I'm not 100% sure what we're looking at here afterward.
JOHANNES: Awesome. Yeah, let's look into that. There is ‑‑ this example already contains a bit more stuff than we might need. But let's read through it. So this file path pattern is, again,, like, a mechanism that allows you to basically ‑‑ the files that you have available exactly.
JASON: Can I get away with this?
JOHANNES: Yep, I think the syntax should be all right.
JASON: Okay. So for folx who aren't familiar with what I just did. The double star means, like, any directory and any file inside of that directory containing .md, and then the question mark after the X marks it as optional. So it can be Markdown or MDX.
JOHANNES: Exactly. One thing that we need to do here additionally, since the original example didn't support ‑‑ or was not based on MDX. Since MD and MDX are a bit more different than just a file extension, and there can be legitimate setups where you want to trade, like, an MDX file still as normal Markdown. Or the other way around. So we now got to also teach Contentlayer which kind of Markdown flavor to expect. So for that, there is a field called ‑‑ I think it's called contentType. So maybe you can just. There you go. Notice there is also ‑‑ if you hover over contentType, there should be some little documentation for it. You see here it defaults as Markdown. So we can here just write MDX. And there should be ‑‑
JOHANNES: If you miss it up and write, like, MDXX, this should even give you some ‑‑ yeah, this even gives you some help here that you mean in MDX.
JASON: It needs to be one of the document content.
JOHANNES: And so the next part feels. This is ‑‑ if you look to one of your Markdown files or MDX files, you have already here defined a title. So chances are that you have, like, some idea yourself of, like, hey, I want to make sure there's, like, a title in all the MDX files. And if it's not, then my page will probably break, so Contentlayer here can help you enforce that.
JOHANNES: You can basically make sure that it yells at you if you forgot about it. We can, like, later see this in action, what this looks like. But you can also say, hey, it's not required and that it's optional. Given that you later use your content as data in your site, this is gonna come in really handy to have, like, to trust like, hey, if I encounter a piece of content or a piece of data here, it's always gonna be there that allows you to keep your site really clean.
JASON: And that's so nice because, like, one of my least favorite things to do is, you know, if we ‑‑ let's say we had, like, pages of one of these. You have to write these really complicated, okay, so, let's get the title. Which is gonna be page.frontmatter. We don't know if that's set. Then we have to check the title. And we don't know if that's set. So then we have to do, like, a fallback, right? These are hard to read. I dislike having to do this sort of thing. It's way nicer to ‑‑
JOHANNES: And probably still mess it up, because now you haven't done error handling before. So there is, like, a lot of things to, like, trip over.
JOHANNES: Computer fields. It might actually come in handy. Maybe you can just copy it out for now. But computerFields from a concept perspective is basically a field that you don't have to specify in your Markdown, but you might want to compute it so that in your site code, it's already there and you don't need to construct it yourself anymore. So in this case, we are constructing a new property called URL where we are leveraging the file path. But we'll come back to that in a second, where it's gonna be more clear.
JOHANNES: But, yeah, so we can save that now.
JASON: Okay. So we've saved.
JOHANNES: Perfect. Now we got just to take care of, like, a bit more setup just to integrate this all. For anyone later doing this, I would probably suggest you get started with one of the existing examples and just modify it to your pleasing. Because, like, then you already have guaranteed working setup. And you don't forget about something. But I think here it's fun to actually do it by hand.
JOHANNES: Whatever suits you better, that's up to you. So the next thing is a little, like, basically to integrate Next and Contentlayer. That's typically done with a nextConfig. And here we're just gonna ‑‑ you can look at the tutorial. We're just gonna copy out some two lines there. Exactly.
JASON: Okay. And our nextConfig was here.
JOHANNES: The one above. So actually, there is one thing, one special thing to consider. Here, we are using not yet MJS, but .js. So we've got to adjust the syntax here slightly. So here you've got to do const. Instead of from, you say = require. Yeah, you know the drill.
JASON: And we're gonna wrap everything here?
JOHANNES: Perfect. Yes. All right.
JASON: Okay. Clear that out.
JOHANNES: That's basically it.
JASON: Okay. So we've got Contentlayer configed. So this is a ‑‑ what's this called? A provider pattern?
JOHANNES: It's called, like ‑‑ it's technically more of, like, a thing that emerged over time and people call it the plug‑in. But really, it's just like something that adds a few ‑‑ adds a few more properties to nextConfig.
JASON: Nice. That's really straight. Whatever your next config is, just wrap it with Contentlayer. Great.
JOHANNES: The next thing is since we're using TypeScript, and TypeScript allows to us make some of the imports nicer. So we're gonna tell now basically create, like, one of these import aliases or import paths. So please just copy the base URL and paths. Maybe copy, like, the ‑‑ since we also need the base URL part. Exactly. So you can add it after the incremental since we're already here in compiler options. Just get rid of the additional compiler options. Line 17 is ‑‑ we already ‑‑
JASON: 17, compiler options. We're already in the compiler options. I understand. Okay.
JOHANNES: Get rid of that wrapping. So that would be all ‑‑ like now we're gonna do one more thing. Which is ‑‑
JOHANNES: ‑‑ include ‑‑ we also. So you can also copy that. We can add this part. And what this will make sure is that TypeScript and therefore Next.js, really more help for Next.js than TypeScript, that this takes ‑‑ also registers, like, hey, content has changed and that helps, like, the live reload.
JOHANNES: So setups should be done.
JASON: All right.
JOHANNES: And you can now get started. So the way how you can, like, tell Contentlayer to do something. You can either do so manually using a CLI, but you would basically not do that unless you have a good reason to. But usually, you just use it as part of Next. It already hooks into your Next stuff, like, very nicely. So you basically now just restart your Next.js server. And then we should at least ‑‑ so, perfect. So now let's stay here for a sec. Okay.
JOHANNES: So, we have ‑‑ now we have ‑‑ we are seeing some stuff here. So Contentlayer was loaded, and we now get some ‑‑ hopefully somewhat helpful error messages already. So I think this has something to do with the glob syntax. I think the glob syntax does not ‑‑ maybe does not allow for this question mark. Maybe it should. Maybe that's a thing to change. If you change that to maybe remove the X and make it, like, a star, maybe ‑‑ let's try that for now. Okay. So that took. I agree this question mark is kind of, like, a common syntax. It's probably ‑‑ I'm using some globing library. Maybe I need to add this ‑‑ this could actually be cool if someone from chat cares about this and wants to create an issue or we do it later, but this should be something that should be supported.
JOHANNES: So now we see here three documents generated in Contentlayer..Contentlayer. So we can now do one of two things. Either we are curious and we want to, like, explore what is the stuff that was generated. Or we can, like, start using it, see how, like, see it in action, and then do the investigation after.
JASON: Let's try it first, and then we'll go look at how you did it.
JOHANNES: Okay. Great. So, the big cool thing about Contentlayer is that it basically reduces, like, all of this content wrangling for you just importing the content. So this is basically the equivalent for something like Gatsby where you have GraphQL as a query language. Here, import is your query language. So you just import it and then you just use it as normal data. So, we import, and we'll, like, we come back to the import what in a second. But if we import ‑‑ sorry. @condentlayer ‑‑ I mixed it up, sorry. Just contentlayer/generated. There you go. And now you can ‑‑ in here, like, trigger the auto completely. Exactly. So we have a few things here.
JOHANNES: So besides the types, actually the interesting stuff here is, like, allPages. So we have now allPages. So now it's really a matter of how do we ‑‑ what do we want to do about it? Given we have right now ‑‑ Contentlayer does rather little in terms of how you ‑‑ how you use it in your app. So, for example, the way how you do your L structures, et cetera, you'd rather solve that with Next.js. So it's unpin rated in that regard. So it embraces the patterns that your site framework uses. So for now, I would say let's not care too much about which page we're using. Let's just display something.
JOHANNES: So what we would do ‑‑ and just for good practice, I would suggest you still route the allPages, take care of that in get static props. Since we're already reducing the at the time that gets built and sent around. So here we can just say page, and all pages and we just take the first one. Exactly. That's great.
JASON: Okay. So then we're gonna get page.
JOHANNES: Exactly. And we probably want to make TypeScript happy. The Next page part, you can probably provide a generic or you can do it like this.
JASON: That didn't work. I need to import it from here?
JOHANNES: Yes. Perfect. And so if you hover here over ‑‑
JASON: Now it's just mad because I ‑‑
JOHANNES: I think what you need to do is ‑‑ after NextPage, I think you need to provide this as a generic there. And perhaps it into the object. And call it like ‑‑
JASON: I'm very bad at this.
JOHANNES: Okay. This is basically just to teach. Yeah, you got it. And now you can get rid of the second page. The TypeScript muscles is basically always the same thing, but you got to get used to it. Yeah, what's cool is if you hover over the upper case page, you should see already the data that you can expect. So this is already ‑‑
JASON: Really nice.
JOHANNES: Based on your point of, hey, ideally, I can just get started without reading the docs. One thing ‑‑ I agree. And one cool thing is that we don't need to go to the docs. You can just hover over this and say, okay, what is the stuff I'm gonna use now.
JASON: Really nice, yeah.
JOHANNES: So I think what would be cool is as a first step, instead of hello chat, we just replace the H1 with the page.title. You've got it. So I would suggest let's open the page. Okay. I think this is just ‑‑
JASON: I think this is because I didn't ‑‑
JOHANNES: Might be overloading.
JASON: Yeah, and I never reloaded the page after we stopped and started the server, too. Okay. So we've got getting started, step 1. And if I ‑‑
JOHANNES: And so ‑‑ yeah, exactly.
JASON: Hot reload. And then ‑‑ now here's the other try. So it hot reloaded again. Let me go into step 1. Hot reload's off the Markdown.
JOHANNES: There you go.
JASON: That's very cool.
JOHANNES: This is, like, can you survive without not having that? With, like, not having that? Probably. But then when you really want to write that blog post, pressing reload, like, for the 50th time is gonna get so annoying.
JASON: Yeah, I mean, it's ‑‑ like, yeah, this is a pain that I've dealt with in just about every project I've ever worked on because I always get it in my head that I need to do something custom. And then that custom thing breaks, I'm always hot reloading, then I don't have it anymore, and I'm like, I can live. I find myself just, like, that little bit of extra friction. This other custom thing I did that's a little extra friction just leads to me not doing anything. You know, I think you called it paper cuts earlier. It really is just one of those things. It's, like, those little points of friction that lead to you just saying, ah, you know what? I don't want to deal with that today. And then you just never ‑‑ you never write. You never maintain things. You don't want to go in and do that. Whether you're an individual or a company, points of your code base or product where nobody wants to do the maintenance because it's just a little bit annoying, those are usually the points that end up being the things that start to make companies feel big and slow‑moving and, you know, it's the beginning of the end, right? If you're trying to stay small and responsive and excited about the work you do.
JOHANNES: Exactly. And this is, like, I want to always, like, optimize for ‑‑ if I have a good day and I have, like, 30 minutes to write something. Let me, like, have a great time getting the thing that when I write out of my system instead of, like, spending 20 of these minutes having a bad time just, like, hating something. So here we just got ‑‑ in case we would just use normal Markdown. Then we would ‑‑ then we could just render it, for example, as HTML.
JOHANNES: We also have the option to deal with the Markdown ourselves again, but here we're using MDX, so we've got to do ‑‑ if you really want MDX, then that's ‑‑ that's great. Like that allows you to bring your own React components, et cetera. We just need to do, like, one more setup thing.
JOHANNES: So we ‑‑ let me just look it up myself again. So you need to import something additionally.
JOHANNES: And that is import from Next Contentlayer for the MDX part. And here/hooks. And so here, there should be a hook called "use ‑‑ sorry. To the import, yeah. Let's see what we've got here.
JASON: I think I froze it. Hold on. What did I do? Oh, boy. It's unhappy right now.
JOHANNES: One second. Just use MDXComponent.
JASON: I wonder why it's unhappy with ‑‑ here we go. All right. So does that mean that up here I can say, like, const MdxBody = useMDXComponent and pass in the body?
JOHANNES: Exactly. The raw source code and the MDX code. So we got to go one additional step code. Exactly.
JOHANNES: And now MDXBody is just a React component that they can use.
JASON: Look at it go. And so what I have done here is duplicated my H1s because I put that in there. So what we're gonna do instead is put the page title in the head. Use the MDXBody for the entire site. So it's full‑control handover to the Markdown.
JOHANNES: But this is really up to you. So, for example, the way that we bill the Contentlayer docs is that we say, actually, in the docs, content never have, like, the H1 tag with, like, a single ‑‑ with a single hashtag or whatever you call it. But we basically just have, like, H2 and smaller. And then use the title ourselves in the content. But that's really up to you. So, yeah, I'm ‑‑ how are we doing on time? How much more time do we have?
JASON: We are doing great on time. We have about 20‑ish, 25 minutes.
JOHANNES: Okay. Perfect. So, yeah, I think we can go a few different directions. For example, we could now bring a Markdown ‑‑ like a React component into our content so that doing the full MDX thing, we could do ‑‑
JASON: Yeah, let's do that.
JASON: So I am going to create a quick folder that we'll call "components," and then I'm gonna do something not super intense. We'll make it, like, a ‑‑ we'll do the ‑‑ why not? Let's do the standard counter. So we will export function Counter. And that's going to return a button. And that button is gonna need an onClick, which we'll define momentarily. And then when you click it, it will be clicked. And up here, we're gonna do, what, like we can do a const count, setCount and useState. Start at 0. And then we need an onClick. So a function handleClick. And when you click, we will setCount to be council + 1. Cool. Great. Handled. So then we have this component, and then what I want to do is in here, I want to use my counter. And in order to do that, I have to import it.
JOHANNES: Not quite. So this would be one way ‑‑
JASON: Oh, okay.
JOHANNES: So that you don't have to do the import here everywhere, my opinion, like, the easier way to ‑‑ there you go. It doesn't quite know about it yet. If you go to the ‑‑ to your index TSX. You basically pass it in there. So now you would basically ‑‑ when you call the component, you can basically provide here a property called "components." And now it's basically an object. Not an array but an object, and now you'd pass in here counter.
JASON: Okay. So we're gonna import Counter from components/counter. And then we will put our Counter in here. And theoretically speaking...
JOHANNES: There we go.
JASON: Boop. Amazing. I mean, and, like, what I'll say about this ‑‑ and what is so nice about this ‑‑ is that we have in a very short period of time just solved a really complicated problem in, like, dealing with Markdown content. Actually, let me just show you the code for getting Markdown ‑‑ I'm using MDX on the Learn With Jason site. And so if we dig into this and we look at the app here, I've got, like, the nested routes. And so then we get our ‑‑ let's see, where is my ‑‑ here is my MDX. So I'm trying to figure out, like, these ‑‑ I have to do a wrapper for the page. I've got my asides and all that stuff. And then when I come back out here, I've got ‑‑ where's my content? It's like we've got our routes. And we've got MDX. And then we've got something like this. And so, you know, we've got a lot of ‑‑ it's just a lot of mess. And then on top of that, when I actually want to list my blog, which is ‑‑ where is it? It's in here. I've got my blog page. Which is a JSX page. And then I've got to do, like, this loading of my MDX. Here's my blog index page where we have to, like, load all of our MDX. To load all of that MDX, I had to write a utility. That utility to load the MDX is here. And, you know, I'm, like, I'm reading the ‑‑ I'm reading file contents. I'm having to, like, parse a directory and get into, like, subdirectories. This is a lot of work. And this is not approachable for a lot of developers because, I mean, like, I have a lot of experience and this was hard for me. I can't imagine someone who is brand‑new to development that they're gonna look at this and go, oh, yeah, no problem. But this, what we just did here, does kind of feel like, okay, somebody who is pretty new, like, that set of instructions makes sense. I can ‑‑ I can get the use MDX component and drop a list of components I want to be able to use into it. That makes sense to me. I get that. This is a big ‑‑ this feels like a big deal. Like it feels like something that really helps in this sort of non‑Gatsby landscape of using this type of content.
JOHANNES: Yeah, exactly. So I think what ‑‑ so what I found interesting is when sharing this to others, they would really ‑‑ it was almost binary in that regard that people that had that pain before, like yourself, they're like, oh, my gosh, I get it. This is amazing. I don't have to deal with, like, all of that crap anymore. But then people who have, like, haven't had that problem yet, they ‑‑ yeah, they're like, which problem does that solve? So I've kind of, like, almost call it, like, pain driven. When you felt that pain, you get to appreciate it. But that's fine. When people ‑‑ when people get in a situation where they need to solve that, I think they'll have a very, very quick and easy time to understand whether this helps solve their ‑‑ solve their situation and help with their setup. And one of the best features, I think, is, like, the simplicity. Since really, like, it's not trying to do a lot. Therefore, it's easy to understand. It's not a new way. And you can just ‑‑ whatever you want to do ‑‑ you can do in Next, et cetera. So it really tries to be focused on sort of the small parts.
JOHANNES: The other thing that I would point out other than the developer experience that we've experienced now mostly. Here, we have a fairly small page setup. We have a few pages. Your own blog, I think you already have, like, dozens of pages, et cetera. And whenever you do, like, a fileread, et cetera, your build times might go up. And if you have a larger project, for example, when we think about a documentation site. So, for example, I think Gatsby has always had, like, really great docs.
JOHANNES: So I've tried to use Contentlayer, for example, on the Gatsby docs content itself, and as a benchmark for the performance aspect of it. So this is really ‑‑ like I've seen sites where a build can take, like, an hour. And that's crazy. Like you don't ‑‑ this is ‑‑ an hour that it takes for you to make a change. And that your users get to see it. And this is where performance was always, like, very paramount for me. It can't just be great developer experience by itself, but it has to be both. So I have actually to, you know, keep myself honest there and keep it transparent, like, if there is a regression, I should know about it. So I've built a little benchmark suite which right now just compares, like, three things. So if you want to go to the GitHub repo of Contentlayer, exactly. And you go one up to the org. And you go ‑‑ scroll down a bit. There is a benchmarks. So here, just illustrating a little bit of the performance aspect as well. So here is, like, we have three setups. So one with Next.js and Contentlayer. One with Next.js. One with Next.js and Remark. It helps with the parts, et cetera. And also for comparison, Gatsby. And we have ‑‑ if you scroll down a bit more, there is some of the current results. And so you see that Next.js and Contentlayer is the fastest for cold build. So cold builds mean you're running build to first the time and there is not, like, a cache directory that it could use to be faster next time.
JOHANNES: And then for a warm build, this is typically where you'd also want to see some good improvements. So, yeah, we've actually ‑‑ yeah, Gatsby has also just submitted a PR that improved their setup of it.
JOHANNES: So I think the warm builds are very comparable and very nice. But I think the thing that's more relevant in our example here is the comparison between doing it yourself, for example, with Remark, and then using Contentlayer. So you see this is a relatively small example, and you already see the difference here. But when it really goes into the, like, minutes and hours that it takes to build something ‑‑
JOHANNES: ‑‑ this is where performance was always, always important for me.
JASON: And so this is 1,000 pages, but this is ‑‑ let's see, 1,000 pages. And we're looking at a ‑‑
JOHANNES: There's, like, no pages that are the same. It's still simple in that regard. That we don't do, like, a lot of, like, code snippets, bedded code snippets or MDX, et cetera. So it's still blended content. The big performance boost comes basically not from you doing for every route. Do, like, another fileread, et cetera. But it basically just processes the content once. And then it just uses Webpack, et cetera, for the import, and there you can do tree shaking, et cetera. Whereas if you do it for yourself, the tooling really can't help you. It's just code that needs to run. If there is a lot of content, then there is a lot of code that needs to run and it's slow.
JASON: Yeah, yeah, yeah. So, I mean, this is ‑‑ yeah, this is very cool. And you can see here that, like, this is ‑‑ so for 1,000 pages, which I would say is a medium‑sized site, right? Like it's not even getting into a large site. Because if you think about how many pages you're gonna generate when you start looking at, okay, I've got all my content, I've got my docs, whatever. It's not small but it's also not huge. That's a 40‑ish percent time savings of switching from Remark to Contentlayer. That adds up. And I know for me personally, one of the things that causes me to be the least productive is if I have, like, a 20‑second lag between when I make a change and when I see the change, I will 100% end up on Twitter and then that 20‑second lag turns into a 20‑minute lag, right? So the ability to see changes quickly, to see builds go live quickly, it just, you know, it's a game of inches, but, man, it makes a huge difference when it's fast.
JOHANNES: And so the cool thing is, one of the things that I haven't even built and released yet, but I've done some experiments, is where I can, like, most modern computers don't just have one CPU. These builds right now for Contentlayer just uses one ‑‑ just run, like, in a single thread.
JOHANNES: But I've done some experiments where I can paralyze things. I've done some experiments, and that suggests we can probably speed this up by, like, 80%. In this case, shave another 8 seconds from the build. So this can make a ‑‑ yeah, performance wise, I have some good plans. And another interesting aspect is that the MDX parser, the folx behind SWC, they might be reimplementing some Markdown tools in Rust. And that could also really make things faster. Anyway, performance, important for me. But let's go back to the ‑‑ to the developer experience side.
JASON: Yeah. Yeah. You know, one only works with the other, right? Like you ‑‑ the fastest thing in the world that's hard to use, nobody's gonna use it.
JASON: And the best thing in the world to use but the output is terrible, people might use it for a while, but it's not gonna last.
JASON: And so, yeah, let's see if we can in our last 5‑ish minutes here. I'd love to look at taking the pages and, like, doing a wildcard route for it. Can we generate the pages?
JOHANNES: So, maybe before writing any code, let's just explore this doc Contentlayer folder that's been generated.
JASON: Good call.
JOHANNES: This is what we're going to be using. Exactly. Already poking at the right point. Here you see the three JSON files that are corresponding to our content. And you see in here, you see the title. You see the body. And you see also some, like, other auto things like the ID and raw, and the raw already seems to contain a bit of that data that's gonna be useful for us here. So you see this flattenedPath, et cetera. This is what we're gonna now leverage as our material to try to craft that experience. And, really, for the most ‑‑ the heaviest lifting we have to do here is rather working with Next.js or whatever you will be using to do the routing, and not as much on the Contentlayer side. So the way how I would typically go about it ‑‑ so there's various ways to do it. I would probably do sort of, like, a catch‑all route. At least for now. And so you don't actually need a separate route for that. So we can just repurpose the index.ts one.
JASON: Okay. Let's do that. So I will delete this one. We'll go in here. And remind me what the catch‑all syntax is.
JOHANNES: The syntax is a little bit funny. And there's various ways. But the one that I use, which basically means that we want it to act both as an index, but also as, like, any other URLs is where you wrap it in double brackets. Then you do dot, dot, dot. And now you give it a thing, like, a name, for example, "slug," or whatever. So after the dot, dot, dot, now that becomes ‑‑
JASON: Oh, I gotcha.
JOHANNES: ‑‑ a variable later. So slug is, like, typically what I use. And now given that it is ‑‑ this is a route corresponding not just with one page but multiple, we got to also tell Next.js which routes they are. So additionally, to get static props, we also need to implement the thing called getStaticPaths.
JASON: Okay. And now this one, I believe, here is my ‑‑ so we need to return ‑‑ is it an object and then paths?
JOHANNES: I think so.
JASON: And then array.
JOHANNES: There are two ways you can do it. Either you render out the path as a string or you return a thing that contains, like, an object with slug. But let's go with the strings first. I think this is sort of, like, the more intuitive way to think about it.
JASON: So we go all ‑‑
JOHANNES: You're generally doing the right thing here. [ Cross talk ]
JASON: And then flattenedPath was the one that I saw that did what we wanted. So this should give us, you know, basically folder structure one‑to‑one is what our paths end up being.
JOHANNES: Let's do one thing just for the sake of ease of debugging. Maybe you can factor line 17 into a variable called paths and then we just console log it. The entire line.
JASON: I gotcha.
JOHANNES: We console log it because it can be tricky to know what's going on here. And, yeah, exactly. That's perfect. And there we can use the shortcut. Let's see where we end up now. So when we just open this in the browser, let's see what's happening. So we see some errors, but we also see this here.
JASON: Yeah, so we got our paths. It's unhappy with something that we did. And we end up with a 404. Okay. That's okay.
JOHANNES: Right. So let's investigate.
JASON: So we need to do ‑‑ what's there? GetStaticPaths is required for dynamic SSG pages. We fixed that. The fallback key must be returned from getStaticPaths. That's okay. I need to add a fallback. Just fallback false, right?
JOHANNES: Basically the routes that are not there, et cetera. Okay. So we ‑‑ this is ‑‑ to be honest, like, also the hardest for me. And this is overall the hardest thing you need to do with, like, using Contentlayer. And it's not even about Contentlayer. But it's rather about, like, bringing together your content and Next.js. But we're gonna get through it. So if we look at the terminal output, we see here the paths that we should have available. So we have getting started, step 1, getting started, step 2, and empty string. So if I remember correctly, let's try by preificing everything with a slash. Let's give this a try. Okay. So this looks already ‑‑
JASON: Doesn't seem mad anymore.
JOHANNES: There you go.
JOHANNES: So, yeah, let's try that.
JASON: Okay. So it's not failing anymore. Let's create one that doesn't exist and make sure.
JOHANNES: There we go.
JASON: So it's only finding pages that exist. That's good. But it's using the wrong data, but that's okay because we hard coded it.
JOHANNES: Yeah, exactly.
JASON: So then what I want ‑‑ is it find?
JOHANNES: Find, yep.
JASON: And then we'll find the page where the page raw flattenedPath = slug, right?
JOHANNES: Kind of.
JASON: Oh, no, because I've got to do the prefix again.
JOHANNES: And slug ‑‑ so what I would suggest is, like, you also log out slug. Just to get a feeling for, like, which ways does the Next system give you access to things here? So, perfect. So here we see slug is an array.
JASON: Oh, I see. Gotcha.
JOHANNES: So what you do is basically from that rebuild a URL string that we can compare.
JASON: Mm‑hmm. Okay. So let's go with const ‑‑ we'll go with pagePath, right? And then we'll do slug.
JOHANNES: We can actually do a shortcut here.
JASON: Yes, please.
JOHANNES: There is a neat thing called "join," which basically tells the elements of an array and joins them together. What you can specify here is, like, the limiter, right? You got it. Perfect.
JASON: So now we're building out, like, there we go. And then if I put in the page path, that should get us closer. Okay. No error there. There's step 2. There's step 1.
JOHANNES: You got it.
JASON: Oh, I broke my homepage.
JOHANNES: Right. So this is the last case and this is where routing is a little bit tricky. So here, it basically can happen that slug is not on the route page. That it's not there. What I would suggest ‑‑ I found it most convenient to ‑‑ when you deconstruct slug in the argument, just, like, make it default to an empty array. You would use equals, I think.
JASON: Oh, that's right.
JOHANNES: So in this way, it basically ‑‑ if it's not there, it's an empty array and still works. So what I think would be cool is for our little page here have, like, a sidebar that's always there that gives us the right URLs.
JASON: Yes. Okay. So we can do that here. And for this, we would want ‑‑ let's go with ‑‑ we'll just do it as a list. So we'll do an unordered list. And then we want allPages.map. That's gonna give us a page. And for each page, we want to return. I'm not gonna use next link because we've got, like, one minute and I don't want to get stuck here. So we're gonna go with here, here, page._raw.flattenedPath and then page.title.
JOHANNES: And we forgot the key to make React happy.
JASON: And so for that, we'll use page._id.
JOHANNES: No, that's right.
JASON: So now we have our links. Step one, getting started. Step two, right? So, tada.
JOHANNES: There you go. The most beautiful page I've seen today, I think. [ Laughter ] The content is there. So a few, like, words on ‑‑ for ‑‑ before we wrap up. One thing that you've done now is you've used all pages directly in your component while you saw that it works.
JOHANNES: It might actually ‑‑ the more pages you can rack up, now you basically bundle this content ‑‑ this data in your app. So what you would want to do is you want to, like, trim it down a bit and route it through getStaticPaths. So, for example, here, you just need access to a subpart of that so you can build yourself a variable that has the stuff you want. We don't need to do it now, but it's just worthwhile know. And, yeah, and the other thing ‑‑
JASON: Can we pull ‑‑
JASON: Can we pull the paths out of here? Like can I just grab it?
JOHANNES: Sure. Whereas we need more than the paths, right?
JASON: Oh, that's true. That's true.
JOHANNES: Here is getStaticPaths. We would need to do it in the getStaticProps. The other thing I wanted to cycle back to, we saw initially this computed fields part in our Contentlayer config. And you were wondering, hey, why do we have it here? And so with that, you saw, like, we repeated this URL thingy all the time. So with that ‑‑
JOHANNES: ‑‑ we can basically define it once. And so here you can just get rid of the posts. And now let's go back. And now we should basically just can do p.‑‑ and you see, url is already here. And you can get rid of this.
JASON: Right. And then we get...
JOHANNES: URL here.
JASON: Very cool. And it stills works. Tada.
JOHANNES: There we go.
JASON: This is cool stuff. Like this is really nice. So, Johannes, I'm gonna send people to your Twitter. Where else should people go if they want more information on ‑‑
JOHANNES: I think Twitter is great. I am, like, I've learned a lot of stuff over the last year, like, where ‑‑ how I've built Contentlayer. But I'm also interested in, like, various other things. So I'm increasingly sharing them on Twitter. And really enjoy talking to people like you to just share what I've learned with others. So I think Twitter is a good spot. And who knows. Maybe in a few months or years, we'll have something other to talk about. I'm also currently building my own music app. I'm building sort of, like, a documentation platform for, like, every package on NPM. So I might have more to share on any of these. Kind of, like, living multiple lives in parallel and have found myself already having too little time.
JASON: Very, very cool. All right. Well, we're gonna call this one a success, y'all. Make sure you go check out Contentlayer, give it a try and give feedback on that GitHub project. I'm gonna do one more shout‑out to Jordan from White Coat Captioning. Been here all day doing the live captions. Very much appreciated. That is made possible through our sponsors, Netlify, Nx and Backlight. Make sure to subscribe to the Google calendar or subscribe on Twitch to make sure you don't miss any future episodes. A lot of good stuff coming up. I'm not going to run through it today. Please, subscribe, follow Johannes, try out Contentlayer. Stay tuned because we're gonna go raid somebody. We'll see you all next time.
JOHANNES: Thanks, Jason. Bye.