Remix Fundamentals

Rendering HTML from Markdown

Kent C. Dodds

Kent C. Dodds

Professional Trainer
Remix Fundamentals

Check out a free preview of the full Remix Fundamentals course

The "Rendering HTML from Markdown" Lesson is part of the full, Remix Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:

Kent demonstrates fully configuring TypeScript for Remix conventions and walks through rendering HTML code from Markdown using the marked library. A student's question regarding if it only executes once in the top level component when using useLoaderData in a child component is also covered in this segment.


Transcript from the "Rendering HTML from Markdown" Lesson

>> All right, let's make TypeScript help us. So, I think the first one I wanna fix is this one here. So, here it's saying object is possibly no and that's because getPost, what if there is no slug by the name of the thing that we're doing? So, if I say 90s mixtapes, then we're reading no of title, TypeScript told us what would happen and we didn't listen, right?

That's why we're getting this runtime error. So, this could be null. So, what we're gonna do is, I say if there's not a post, then throw a new error, post not found. Now, this is not the end solution, we'll come back to this later but this is what we're gonna do for now.

So that now our server can give us a much more useful error message than the user saying, cannot read no property title of null or whatever, at least it will say hey, post not found. See, that's at least a little better user experience but we're gonna make one that's even way better.

But then, TypeScript also is happy about that, because it knows that there's no way that a falsie value can make it past that If statement, right? So, we're good there. Now on the slug, the slug could be undefined now, why is that? That's super frustrating. Why could it be undefined?

Isn't Remix gonna make sure that the slug is always a string? Yes, Remix will. But TypeScript doesn't know that. And so this is one area where we have to do something a little extra to appease TypeScript. And I'm actually totally cool with this because it's very easy for somebody to come in here and change this to anything, right?

So, it would be cool if we could teach TypeScript that convention because then it could protect against even that. But because we can't, then we have to do something like this, if not posts params slag then throw new error, missing slag, yeah, whatever you want it to be.

And so now, we get post not found. I can't actually reproduce a situation where the slug is not defined unless I change this to this. And now there we go missing slug, right? So now, TypeScript is not angry with us and it can actually help us out and we get autocomplete, which is kinda nice, cool?

Okay, the last thing that we're gonna do is render out the actual content. I think I skipped over, I did these in the wrong word, but we write our content in Markdown and we wanna turn it into HTML, so we're going to use this library called Marked to do that processing for us, it's pretty.

As far as Markdown parsers go, it's pretty simple one. So, we'll get our, oops, HTML from Marked with post.markdown. And then here's our HTML. And then, here let's just get our post in HTML out of here. And then we're gonna use dangerously set inner HTML, and I know that sounds really scary but it really is not, and I'll explain why.

So, what we have here is HTML. Let's see what happens if I say div HTML. I'll start with the problem first. So, this is what happens if you just stick HTML in a div in any modern framework, it will automatically escape that for you so that you can't have some sort of user come in and be like, script alert, gotcha, right?

We don't want people to be able to do stuff like this, that would not be good. So, React, Angular, all of them, will automatically escape this for us so that, or at least they may not automatically escape it themselves, but they ensure that it gets into the DOM without actually executiong as HTML.

This is good because you wouldn't want, this actually happened to me in real life. You wanna hear a fun story? So, when I was at a company called Domo, we had a chat feature and I got a bug across my desk that said, hey, there's a cross site.

This is called cross site scripting. That's what we're avoiding here. There's a cross site scripting vulnerability in our chat, you need to fix that, because if you don't, then somebody could steal somebody else's credentials and see all their data and that would not be good especially a demo because it's like a company business intelligence thing as like literally all the data.

So, as a good engineer, I need to reproduce it first, right? To make sure that I know what what bug we're talking about. So, I reproduce it by typing in the chat, script alert, you done been hacked, is what I said. But I did it in our production environment.

Luckily, it was just like our own internal instance of Domo. But there are 500 people who on every page, they would go to anytime they moved to a different page, the chat would load up again and they'd get this alert that said, you've done been hacked. And we hadn't yet implemented delete chat yet, [LAUGH] and so I'm running around for two hours trying to, somebody please delete this chat message.

Yeah, that was an embarrassing day. And that was Backbone JS, so Backbone did not auto escape stuff and that's why we had the vulnerability, to begin with. So, for that reason, all modern frameworks will not just take HTML as face value, you need to either escape it yourself like you are taking user input.

Or if it's not user input, which is the case here, we're the ones writing the markdown. So, we're not gonna hack ourselves, that would be silly. So, we are actually safe to use dangerously set inner HTML. But that's why it has the prefix of dangerously, is because you have to know what you're doing to use this.

We know what we're doing, we're not doing anything wrong, so we're fine. Okay, great. So with that said, here's my list, and we've got UL allies and on this one, okay, first post isn't this great, question mark, I think I have a semicolon. [LAUGH] There we go, cool.

Any questions? So here's something that's neat. You might not have thought about this. But this is all happening at runtime, right? No build time stuff. We have, in our database, or our Markdown, we're turning it into HTML and sending that to the client. So, not only have we saved, I mean, we could probably optimize this further to just send the title, post.title.

There we go. Now, we've really optimized what we're sending to be only the stuff that we want, but also we're reducing the amount of compute the user's device needs to do because we're doing all that compute in our loader. And in a world where we're talking about mobile devices and stuff, that actually is pretty significant.

Additionally, this library is not free, but it's only being run on our servers so, we don't have to pay the cost for downloading of that library. I think that's pretty rad, yeah.
>> If I use useLoaderData in a child component, will the loader only be executed once for the top level component?

>> Yeah, so, we will not fetch data. In fact, useLoaderData is not what triggers the data fetch, useLoaderData is called after the fetch is already done. So we don't even render this component until the fetch is finished. So yeah, you can call this as many times as you want, no problem, no big deal.

Cool? All right, that's Exercise 3.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now