659

August 28th, 2023 × #webdev#javascript#ogimage

OG Image Options

Scott and Wes discuss techniques for dynamically generating social media preview images known as open graph or OG images using services like Satori, Cloudinary, and Puppeteer.

or
Topic 0 00:01

Introducing Syntax podcast

Scott Tolinski

Welcome to Syntax.

Topic 1 00:12

OG images overview

Scott Tolinski

In this Episode, we're getting real OG with it. We're gonna be talking about OG images and some interesting ways that you can, rid. Work with image OG images, what they are about, what's going on with them in general, and some of the techniques for doing dynamic ones, rid. Ones that can be unique based on the content in which you're arriving on. My name is Scott Tolinski. I'm a developer from Denver. With me as always is Wes Bos. Wes, what's up? Rid Hey. Not too much. Excited to talk about

Topic 2 00:44

OG image sizes

Wes Bos

OG images. This is actually something that popped up when we rid We were just having a little chat in our Syntax, Slack room about it. And we're like, there's actually a lot of Ways to do OG images, and

Scott Tolinski

they all have their ups and downs. And I have a bit of a crazy way I cooked up my version, which We might be using for syntax. We'll see. I think we should be using this for syntax because when when you presented this to me, which we'll talk about in a little bit, I'm just thinking, like, why isn't anybody else doing This way, this makes so much more sense in many ways. So we'll be talking about about that. But first, let's, take a quick minute to talk about Sentry.

Topic 3 01:21

Sentry sponsors Syntax

Scott Tolinski

Sentry@centry.i0 is the perfect place to check out all of your errors, anything that's going on in your application. You run-in production rid. You need a tool like this to make sure that you aren't shipping errors. And if you are shipping errors, that these things get resolved in a timely manner Because you can get stack traces. You can get AI help. You can get performance metrics. You can tie them to a specific release. And, heck, once you Find this issue. You could send it right to GitHub and make a GitHub issue and attach a user directly to it.

Scott Tolinski

It's it's incredible software and software that's improving and updating and changing all the So check it out at century, s e n t r y, dot I o. Use the coupon code at tasty treat, all lowercase and all one word, and you will get 2 months for free of this incredible service.

Scott Tolinski

And, Sentry, we are actually a Sentry company, Syntaxes. So if you wanna help support Syntax, Go sign up for century and let them know that you found out about century through syntax.

Topic 4 02:18

Open Graph protocol overview

Scott Tolinski

Okay. So OG images, what makes them gangster? What makes them OG? What what's going on with OG images? Well, it turns out Open Graph is what OG stands for in the OG of OG images.

Scott Tolinski

An open graph was a standard standard in quotes here created by Facebook in in 2010, and it's basically just a way That, like, Facebook and, they were I don't know if they were thinking about other social media sites, but maybe they were. They were thinking like, hey. When somebody pastes a link, It's kind of ugly just to have a link in there. Right? Like, you're not you're not giving enough information about what this thing is, especially when you're posting to social media. We all know, Like, graphic image or or those types of things are more visual people are more likely to click on things and interact with things if it's Not just like a little blue link in a text box. Right? Mhmm. So they created this thing called Open Graph, which is, basically, a way of adding metadata that social media site can latch on to, and then the owner of that site can then choose how they want to display those things.

Topic 5 03:24

OG images defined in meta tags

Scott Tolinski

So if a image pops up or you paste a link somewhere, a social preview or a preview of that link will pop up rid with whatever image you have, and these are called an OG image. The OG image is the image that pops up, and they're defined via a meta tag.

Scott Tolinski

So I I actually didn't even think about this. It's so funny. We have our meta tags in HTML. Right? All the meta tags we we used to use for SEO and who knows what. Right? Mhmm.

Scott Tolinski

But it it makes sense that you could just have a meta tag and kind of, like, hijack that to do something that you wanna do yourself because all they did is have a property of OG colon and then whatever the meta property is. So the OG image is OG colon image. And then Facebook, Twitter, whoever, Slack can choose to latch on and load that image how they see fit, and they name spaced it with OG for open graph. Yeah. It's rid. Quite a wild world. There's a lot of different

Topic 6 04:21

Open Graph metadata options

Wes Bos

items you can put in there. You can say, like, what type of content That page is is it a video? Is it a photo? Is it a book? Is it an article? You can link up all the different values there. But rid Like like you said, the one of them is the OG image that's we're talking about today, where you can specify what that image that shows up when you share something.

Wes Bos

Rid. You can also set like widths and heights on them as well because there is you're probably used to the like 16 by 9 or 16 by 10 rid version, but there's also a square version that will show up when you share something on

Scott Tolinski

what is it? Thumbs on Twitter. Twitter. Yeah. One is pretty popular.

Scott Tolinski

Yeah. I think Facebook too. I was gonna say what's interesting is that the, like, the recommended, aspect ratio for an OG image is 1.91 by 1.

Recommended OG image sizes

Scott Tolinski

It's like, okay. Sure. That's an interesting aspect ratio. I, yeah, I don't know why. And the recommended minimum resolution of an OG image is 12,000 by 6 30, rid. 630 tall, 12,000 wide. Right? Which is it ends up being, like, 1.912, whatever. It's, like, not even, like, a Smooth even fraction. So, like, if you multiply 6.30 by 1.91, it doesn't even equal 12,000. So, you know what? Honestly, I have no idea why They chose that kind of aspect ratio or or anything like that, but, hey, that's just that's just the way it is. So, basically, if you're making one of these things, The recommended minimum resolution of it is, is 1200 by 6:30.

Scott Tolinski

Weird.

Wes Bos

It seems kind of Kind of small, don't you think?

Scott Tolinski

Oh, maybe not. No. That's not small. I don't know. 12,000 or

Wes Bos

12 No. It's the YouTube thumbnails That still recommend that you do 720.

Topic 8 06:11

OG image size discussion

Wes Bos

Something like that. And it feels like that's rid. Should be much larger. Like, it should be 4 k, shouldn't it? Mhmm. Yeah. Give me as big as you can there, but yeah.

Wes Bos

Let's talk about testing them, like, if you want to be able to to test, and then we'll talk about, like, how do you actually set them up.

Wes Bos

Polypane is rid. A really cool application that we we had the author on the podcast.

Wes Bos

I also met him. He's Dutch. I met him in the Netherlands a couple months ago, so that was pretty cool. And that is a nice app to be able to preview them. And he recreated all of the logic of rid. Displaying Open Graph images in the application, which I imagine is not fun or easy at all. Yeah.

Topic 9 06:52

Testing OG images

Scott Tolinski

Rid. I I primarily use Polypane for testing them. There's also this meta tags dot I o site. You can just paste in any URL, and it's gonna load up showing you how all the meta tags Working function on your site, which is great. I mean, Polypane does that too. If you don't wanna use a, a full browser like Polypane, you can always just dump your URL in to see this and see exactly what's Going on? Is it getting the right images? Is it loading them up? Always a very useful thing to do, but that one's gonna be for, like, you know, sites that exist online already. So if you're working locally, I I probably will always lean on something like polypane myself. I like the way that works. It's very nice and shows you everything. Rid. Twitter and Facebook

Wes Bos

both have their own, debuggers, they call them, which are helpful tools.

Wes Bos

Rid Twitties used to preview it, and it would scrape it and tell you the output. The Facebook one does that. It'll tell you, alright, these are the meta tags as I, Facebook, sees them. Rid And Twitter now tells you just paste the link into Twitter, and you can see how it looks.

Topic 10 07:54

Debugging OG images

Wes Bos

But they both have rid Tools to invalidate them, which are really handy because sometimes you share something with, like, test, test, testing 1, 2, 3, or, like, rid. The only image on the page is like a photo of, like, your uncle. And then like, oh, well, I'll use that as the Open Graph image, you know. And you need to, like, purge that information in Facebook and in Twitter. So the tools you can invalidate the data and it will do a fresh rid. Fetch. If you've changed your tags since that's nice. A fresh fetch. We always like a fresh fetch.

Scott Tolinski

What about, like okay. So If you're out here and you're thinking, okay. I'm gonna make my my OG image. Here's the the simplest, most the basic path you can possibly do for this type of thing.

Topic 11 08:28

Basic OG image setup

Scott Tolinski

Rid. You create an image that is 12,000 pixels wide, 6 30 tall. You have it be Something that visually represents your website, your entire page.

Scott Tolinski

The entire website could just be your like, the current one for syntax At the moment of recording, this is just, like, basically our logo on a Grunge. It's like an a wide version of the podcast art that you you typically see when you subscribe to Syntax. If you do that, you get that up and running, you paste links. Hey. It's gonna look way better than not having 1. So if you wanna do you know, if you wanna just get something up For your OG images, that's the way. You then need to just reference that image in the meta tag on your site along with any other metadata you want to have with Open Graph, and you're done. But I think we're here to talk about maybe the the less basic aspects of this. What are, like, the more Dynamic ways you can do this. You'll often see Yeah. Times people on Twitter, they'll post links to, you know, a video or a blog post or something.

Scott Tolinski

And the image, the big old OG image will have maybe a photo of the author, maybe some text, maybe a date, maybe, you know, tags or who knows what. It will be Graphically, an image that is there to represent the individual piece of content itself, and this is a world That has gotten very interesting. Yeah. It

Topic 12 09:41

Dynamic OG images overview

Wes Bos

is likely that you are not going to create them. I used to do that with my blog as every single Blog post, I would create an image. Really? And, like, that that's good because it, like, it helps When people are viewing it, but, like, that's just another thing to have to do. And it's probably better if you have lots of content and lots of pages rid. To be able to dynamically generate them into a single image, like Scott says, you won't have the author. So, like, on my website, if you share out one of my blog posts, it has rid The URL, it will have my logo. It'll have my photo. It will have an image from the rid. Blog post, if there is one. It obviously has the title of it, and and you get the point. Right? All of that that information. Like, even with, rid The Syntax podcast. Like, we could take the waveform, literally of Syntax rid And put that waveform on the actual thing. Right? Like, you could you could visualize it that way. So there's lots of different services out there That will help you generate these things or packages, services, and methods.

Wes Bos

But at the end of the day, you need to dynamically generate rid a PNG or JPEG and kick it out the door when one of these services requests something from you. So the first one we have here is Satori slash rid Vercel OG. So Vercel OG is a React component for creating these.

Topic 13 11:14

Satori/Vercel for generating OG images

Wes Bos

And then behind that Vercel has this package called Satori, which is the engine That will generate them.

Wes Bos

And the way that it works is you create a card And you pass it, a bunch of CSS of where you want it to be, colors, font sizes, fonts, all that good stuff. Rid. And then you run it through Satori, and out the other end it actually kicks out in SVG, I believe, which is pretty cool. So you could very quickly Take your input and output some, like, really nice looking thing.

Wes Bos

Seems to be a package that a lot of people will recommend, and I'm pretty sure Vercel is also has, like, a hosted version of this as a service You can just send your information to.

Wes Bos

I don't think it's it's out of beta yet, but it's something that they've been teasing for a while.

Wes Bos

But the downside to this is that it's not full HTML and CSS. The layout engine that they use is called Yoga, And yoga is a layout engine behind React Native. So if you ever tried to style something Mhmm. In React Native, you'll know, okay, I can use CSS properties here, but the support for each of them has to this thing has to support rid. The equivalent to it. And if you look at the code base behind Satori, they have files for pock shadow and for rid. Line height and, like, they have basically every single CSS feature that they need to support, clip path, all that good stuff, somebody has to figure out, okay, how do I this to,

Topic 14 13:10

Satori limitations discussed

Scott Tolinski

I believe it's SVG out the other end. Yeah. It is SVG. And and there's, like, a lot of interesting things. I mean, in the, The repo, you can look at, like, what's supported and what's not supported. And to me, this is the biggest downside of this this project is that it isn't just rid Your your normal tools, which, you know, I mean, we're generating images here, and that's fine. But you do run into situations where It it's a little frustrating to work with. Granted, you're generating an image. The generating an image from DOM content, not super easy in a lot of ways Regardless, so the fact that you can do this with this simple of an interface is great, but I did find it a little annoying to work with, especially if you get into edge casey stuff. Like, for instance, you can load custom fonts in here. But if your custom font is 1 not on Google Fonts, you know, or not not a standard font. You know, I had to load each individual font, each individual font file as an array buffer to pass an array buffer under this thing, and it it's just obnoxious. And on top of that, I found the font rendering to be, like, rid Pixelated on top of that, and it could just be the font that I'm using or how I'm loading the font or whatever.

Topic 15 14:22

Cloudinary for OG images

Scott Tolinski

But I did find situations where it wasn't perfect. Mhmm. And it That's fine because a lot of their examples do work really well. And I think if you're on the happy path for this thing, it can be like a massively powerful tool. But the moment you get off that happy path and you try to do, you know, some more edge case y things, it does start to get obnoxious. But, again, you're generating an image. What's cool about this is being able to use this with, the fact that you can have access to rid. Itself and not have to be reliant on React for this is that you you do get access to some really neat tools. So one of the things that I was working in was within Svelte to basically build a system that take you have a Svelte component, And Svelte components are easy enough that you can run them through, like, a renderer to HTML. So you could just import a Svelte component. You can pass data in there. You can say, hey. Render it with this specific data.

Scott Tolinski

And then let's go ahead and then, take that And create an SVG out of for from Satori.

Scott Tolinski

And then from there, you can take that image and generate a PNG out of that, rid SVG image using a function directly from the Satori package.

Scott Tolinski

So at the end of the day, what we would just have would be a server side route That takes data from our database, passes it into a component, renders that component to an SVG, Then we render that to an image, and then we return that as the end result of the route as a image PNG.

Topic 16 15:55

Level Up Tutorials OG image approach

Scott Tolinski

And, hey, it worked. We had dynamic images going on this thing.

Scott Tolinski

That said, you do run into just, like, little situations where it it can feel pretty, rid

Wes Bos

Restrictive sometimes in working in. Yeah. I found the same thing when I built if you look at any of my, like, TikToks rid or Twitter videos. You'll see that I have, like, text that is overlaid on top of the video in the thumbnail. Mhmm. So I built. Rid I was, like, I was doing those in in Figma every single time. I was, like, this is annoying. So I built something that laid it out in HTML and CSS, rid And then I made, like, a, 9 by 16 version. I made a 4 4 version, and then It just, like, resizes it and whatnot. I use HTML to Canvas to basically lay it out in HTML and CSS, and then I use HTML to Canvas to Essentially, they just, like, redid the entire layout engine in Canvas. Mhmm. That is CSS.

Wes Bos

And it works, like, 87% of the way there or 92% of the way there, but, like like, rid Line height was off, you know, when I would when I would render it, and letter spacing was a bit weird and line wrapping. And then as soon as a new Piece of CSS comes out like, what is it? Like, container queries. Like, this is a perfect use case for container queries is that, like, if you wanna rid Size the text's dependent on how big the OG image is. You can use container queries for that, then you gotta wait for these packages to support container queries and You know, so you're right. Like in a lot of cases, especially if you just need like a corporate text gradient, rid You know, you're good to go, but I always found that to be a little bit limiting when I I'm like, I just want full ass HTML CSS. Rid. And I don't necessarily care about the speed because OG images are not blocking anything.

Topic 17 16:53

HTML to Canvas for OG images

Wes Bos

The only person waiting on OG images is Twitter, Facebook, Slack, Discord, and they just have to wait an extra For in my case, we'll talk about my puppeteer example.

Topic 18 18:05

Puppeteer for screenshot OG images

Wes Bos

Sometimes it takes an extra second or 2 seconds to render the thing out, but

Scott Tolinski

rid Literally never an issue. Yeah. Hey. It's it's a like I said, a happy path is is very good in in Satori. Like, the rid. Versions of the images that they've been able to create with Satori are very impressive, and so, like, the upper limit of what you can do with it is really great. So you you can get some awesome stuff. Now there's an other alternative route, and this is what we did on level up tutorial. So I I have direct experience with Satori in this version, which is something like Cloudinary. And Cloudinary Has the ability to do a lot of cool stuff too where you are basically assembling an image via commands in a URL, Which is not a lot of fun to do that, and you probably won't, in practice, end up actually just writing The URL, unless you're, like, really confident with Cloudinary URLs because you end up having to do, like, groups and layer type of deals with that, and it can get really, re Really confusing in the URL space. So there is a node package that allows you to pass in parameters to how you want things and different items in your image to be laid out, and then you run it through a function essentially, and it spits out a URL of your image.

Scott Tolinski

And that's what we did on level up tutorials. It was really nice and easy. It worked well enough, but the problem there is, once again, You don't have access to CSS straight up. But so you end up having things like and and this is actually a thing I don't understand why they chose to do this, rid because I like Cloudinary a lot. But, like, when you're when you're choosing to to lay stuff out, the layout system is is unfortunately very difficult to work with, And you end up with really weird properties like gravity, west. Gravity is equal to west. Why would they choose west? Yeah. Southwest, northwest.

Scott Tolinski

Why? What are we doing here? Why why did we choose gravity west? One, gravity. Okay. I get it. What? You can do like a line center, a line left, a line top left.

Scott Tolinski

Gravity west? I I don't know who's behind that choice, but I don't I don't agree with it. And then you also have things like, like weird properties, like different types of flags. Like, oh, I want to I want to encapsulate all of these transformations I've done and now apply it to a layer.

Scott Tolinski

And if you're not a Cloudinary pro, I honestly I I took me a day of ripping my hair out to to get this even with their, like, visual editor. And once I got it, granted, the end result was great. I had layered images. I had, fate you can do things like face cropping. So you can say, hey. Crop to this person's face, Which was really great for authors. Right? I got their face cropped in a circle. I got their knee, you know, nice digital or or nice background images, and everything looked Very, very nice. The end result was there for me.

Scott Tolinski

But it was the process of getting to the end result was was Not the most fun aspect of it. So there's a lot of other options out there. We're not gonna go through every single one. But

Wes Bos

as you can understand, there's rid packages that you can do it yourself or services that will help you do it and offload the sort of heavy lifting.

Wes Bos

Rid One more option, which is pretty popular, which is using Puppeteer to literally just take a screenshot of rid A page and use that as it. So that's exactly what I've been doing for my website for probably 4 years now. And I am rid I'll tell you. I'm surprised at how how good this works.

Wes Bos

So the way that it works is that I have I literally have a page on my website that is I I think it's westboss.comforward/thumbnail.

Topic 19 21:38

Wes' Puppeteer OG image approach

Wes Bos

I'll link up in the show notes an example page.

Wes Bos

And via the query params, I I pass the number of parameters, the the title, the rid thumbnail, the URL, any other pieces of dynamic information.

Wes Bos

Then I just use regular old HTML CSS CSS Grid rid. Layout to layout the actual card itself. So the card can literally be any size, and the the card will adapt to it.

Wes Bos

Rid so that's just a page of my website that is a dynamically generated page. Then I use, Puppeteer. Now Puppeteer is rid Headless Chrome.

Wes Bos

And the way that that works is that you can you can run a Chrome browser rid. On a server, and people will often use this for doing testing or automating a bunch of things.

Wes Bos

Ready. And what I've used it for is you fire up a Puppeteer instance, and then you can say go to this page, Wait for these things to be loaded and then take a screenshot and then do whatever you want. So I have a serverless function That loads up Chrome.

Wes Bos

And it was a bit of an issue recently because Chrome is now past 50 megs.

Wes Bos

Rid. So I had to find some crazy, custom build of Chrome that didn't have rid 3 d in it or something like that that got small enough. So that that's a bit of an issue. There's other things you can do. You can load Puppeteer in A layer on AWS Lambda or you can Cloudflare now has Puppeteer support where they've reimplemented the whole Puppeteer API in their own version. Rid But so what that does is it takes a screenshot in the it loads up Chrome, takes a screenshot, and then rid Out the other end, you you get a an image which is stored in a buffer. And, like, a buffer is just a representation of a file That has not yet been saved anywhere. And then once you have that buffer, you can do you can send it to Cloudinary if you wanted. You can put it on, rid you can put it on your own server. You can put it in an s 3 bucket.

Wes Bos

But then you have to, like, manage all this, like, invalidation rid API, like, how long does that sit there? You know, how do you delete them? What happens if what what happens if one of them doesn't render properly and then takes a screenshot of the wrong thing? So what I did is outside of your serverless function, like outside of the request handler, I created a map. You can use an object as well. Rid And then I just stick it in that map, and it stores it in memory indefinitely until that serverless function spins down. Rid. And from what I found, it will stay warm, meaning that the memory will stay there for up to, I forget what it was. I think it's, like, 4 or 5 hours or something like that.

Topic 20 24:45

Caching OG images in memory

Wes Bos

So then when somebody visits that page again, it says, hey.

Wes Bos

Rid Did I generate this one already? Oh, yeah. It's in memory. Send that as a a JPEG out the door.

Wes Bos

Rid Otherwise, go ahead. Do the whole song and dance of of regenerating it. It takes a second or 2 because you have to load up you have to load up the entire Chrome browser. Right? Rid. And it's worked out really, really well, and it just regenerates them the next time somebody requests them. And it's not because people are not sitting there waiting on it. Like, if I'm not using them for thumbnails on any user facing pages. It's just Discord, Facebook, Twitter, being able to render them on out. A couple times it's broke rid Because of upgrades that's happened like I just talked about. But otherwise, it's been pretty good. And I have full Like, I I do all kinds of, like, clipping, and overlaying of images and grunge and and rotating things, and rid It's so nice to be able to have full HTML CSS control. And debugging the layout on these things is so easy because you can just use dev tools. Yeah.

Scott Tolinski

That is honestly, when you told me about this, I was thinking, this solves all of my problems. I don't have to worry about font rendering. I don't have to worry about, you know, nonstandard layouts.

Topic 21 25:50

Benefits of Puppeteer discussed

Wes Bos

You know? Sure. It has its own challenges. Emojis don't even work in Satori. Right. So you gotta, like, load emojis.

Scott Tolinski

Exactly. There's so many little things that these other issues or these other stranded so when you told me of this Approach. It was like, why isn't everybody doing it? Like, why why are people you know, why are people struggling, rid. To do all these other techniques, it seems like a really, really good approach that I just do you don't you don't see people talking about. Mhmm. And I guess the other thing is they're all it's all done on demand as well. I guess a lot of this is now on demand. But what at the time,

Wes Bos

everybody was on the, like, prerender, rid You know, prerender your website. Yeah. And I was like, I'm not gonna I I have probably a 1,000 pages on my website. I'm not gonna render rid Open Graph images for a 1,000 pages. I just need to do on demand. So when somebody needs that, it will it will be generated. Rid. The only other thing I'm wondering about is for when we do it for syntax is can a) can we look into the rid. Cloudflare version of this so that we don't have to go down that weird rabbit hole of trying to fit it in.

Topic 22 26:37

Considering Playwright

Wes Bos

But I guess if if we don't run it in a serverless function, then we don't necessarily have to care about that, right? Because it's that 50 meg limit is not an issue for us. It makes deploys a little bit longer because you have to NPM install a 50 meg Chrome.

Wes Bos

Some people also use remote versions of that. But then there's what's that other version of Puppeteer that works on all the browsers? Oh, that's a good question. Playwright. Playwright. Yeah. I also looked into Playwright when we were doing the like, I was playing with the transcript video recording, and rid. Playwright also has a whole set of APIs for recording and screenshotting and all that and whatnot. And it seems like Playwright is the Probably the better one of the 2 these days. It's it they recommend it as an end to end testing platform, but it's it's a headless browser for literally every browser. So you can have Headless Safari, Headless Firefox, Edge, or Chrome, and you can take screenshots. The only reason I didn't go with it for that Project I was talking about was because Playwright doesn't record audio if you're doing video recording, which is screenshots. That's all we need. Right? Yeah. Right?

Topic 23 28:26

Other OG image techniques

Scott Tolinski

Wow. What a fascinating, what a fascinating strategy. If you're out there and you're doing OG images in a way that's not Cloudinary, Satori, or something with screenshotting via a, headless browser like this. I wanna hear what you're doing for this because, rid I don't know of any other techniques, and this is an area that I'm particularly interested in exploring right now. Oh, one other one I forgot was some people

Wes Bos

rid. Use the Canva API, which I thought was really interesting because if your designer builds the thing in Canva, rid. Then you can just pipe in the variable parts via the API and get a JPEG out the other end.

Topic 24 29:08

Canva API for OG images

Wes Bos

So that's that's pretty cool as well. Obviously, I like HTML and CSS because that's what I'm most comfortable with having full control. But if somebody is like, I'm gonna design the open graphs rid in Canva instead, then you could use that API. Okay.

Topic 25 29:23

Outro

Scott Tolinski

Sick.

Wes Bos

Rid Canva. Yeah. Alright. That's it for today.

Wes Bos

We will catch you later. Peace. Peace.

Scott Tolinski

Head on over to syntax.fm for a full archive of all of our shows.

Scott Tolinski

And don't forget to subscribe in your podcast player Or drop a review if you like this show.

Wes Bos

Rid.

Share