skip to content

Build a Dynamic Links Page for Social Bios

Putting a link tree in your social bios is popular, but what if you want it to live update with your stats? In this episode, Jason will build out a custom "link in bio" page with dynamically updating profile stats.

Full Transcript

Click to toggle the visibility of the transcript

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. We're going to play with we're solo and we're going to play with, I want to build a link tree. Let me talk about what it is and the stack, I have so ideas. That typescript show was super fun, I'm thinking I might try to write some typescript today because it was fun. But so, OK, here's what I want to do. On profiles like Twitter, Instagram, TikTok, whenever you're in some of the social profiles, you get the opportunity to put one link in. And like, for me, I don't know where to link people to because if I link them to Jason.af, that's my writing and my bio, which is relevant, sometimes, but other times I'd want people to find Learn with Jason because that's more relevant if you're a developer, you want to see the educational content. Sometimes, I want you to go to Twitter because Twitter is more casual, it's where I'm talking about food and hobbies as well as code. And sometimes, I want you to go to like, very rare cases my TikTok which has exactly one video. And you know, I don't know. I don't know where I want people to go. So what I've seen people do to solve this, they use, there's a tool called link tree or they'll build a page on their site that's like links or something like that and then you put that in your social bio. And when you click it and open it, you get a list of my blog, my Twitter, whatever. And I think that's cool. I like that. And I've started to feel the need for it where, like, for example, when I say, hey, it would be really helps the show if you like and subscribe, and you know, things like that. Which hey, nudge, nudge, wink, wink, click the buttons. Am I telling you to do that on Twtich? On YouTube? Yes. Am I asking you to subscribe to the newsletter? Yes. It's not clear because there's not a link to it. Where do you go to click, right? I can fix that. If I give you a LIPGs page, go to LWJ/links, which doesn't exist yet, you can have a link to the newsletter page and Twtich and all of these other things that would be, like, a way to show support for the show that doesn't cost you any money.

Those are OK, great, that's a useful thing to build. And the other piece of that is, you know, there's this social proof aspect of saying, hey, you're not the only person who's supporting the show, you're part of a big group. And so this is the part that feels like marketing. So it's a little uncomfortable. But it's like clearly a thing that you that works and that people do and that makes sense. Is to show how many other folks are doing this thing? It's nice to be part of a group. And so, you'll see this where you go to a website and it's like thousands of developers use this tool. OK. Great. Exactly. So can we do that with links page? Can we show an up to date Twitter follower account without getting rate limited? Without shipping a bunch of JavaScript client side? And I think the answer is yes. So that's what I want to build today. I want to figure that out. And so, my wait, we're buffering today? Hmm. I did, yeah, chocolate banana rhino, I did go live. We've been live for about 3 minutes now. I'm not yeah, my bit rate seems OK. Let me know if you keep seeing buffering and we'll dig into that. But yeah. OK. No buffering over there. All right. Well, sorry you're seeing buffering. We'll forge ahead and the recording will be available if it's just not working out. Sorry if that's not working. It is short buffering, that's let's keep an eye on it. Looks like my bit rate was a little lower than usual at the beginning but seems to be leveled out at the normal rate here. So we'll keep an eye on that and see how it goes. Hopefully, my internet does not betray me. Don't do it, internet. Don't betray me. OK.

So that's what I want to build is a the link in bio thing. But in a way that doesn't require a server and doesn't use my serverless function calls. What I'm looking at doing is using Edge functions for this. I think we should write some typescript, we did that episode with Matt Potock, I'll pull up a link to that. It was a great episode. Typescript, I can't spell, that's what's going on. OK. Let me drop a link to this in the chat. Here. Right. So that episode was great. I'm going to use some of what we learned there to see if I can write typescript today. I'm going to see if we can do it in astro. Here's what I want to do, I want a way that's easy for me to update the links so that I'm not copy/pasting the same block of HTML around. Although, really, this is simple enough I probably don't need to use anything. But then, I want it to just be HTML so that I can, then use the Edge functions to rewrite the HTML in a way that doesn't force me to work around hydration errors. And also, this is a super lightweight page. It doesn't need JavaScript. So my goal is zero client side JavaScript, but I still want those dynamic updates. I want to show my Twitter follower account, the number of videos on Learn with Jason, the number of blog posts on Jason.af. We're going to set up an Edge Function and some serverless functions to get those stats, but I'm going to set those to cache for an hour. Honestly, some of them, I can cache my number of videos for a day because I don't make more than one video a day. I could probably cache my number of blog posts for three or four days. With my current rate of publishing, I'd cache those for a month. Sorry. [ Laughter ]

But, like, Twitter followers, maybe I want to update that hourly. YouTube followers, subscribers, maybe I want to update that hourly because that number changes enough that it's maybe worth updating. So, that's the outlay, that's the goal. And we've got let's check the clock, we've got can I do math? It is 9:37, we've got 83 minutes, call it 80 minutes with the outtro. Can we do it? Let's put it on the clock and see what we're capable of. Over to the screen share here. And let's start with a quick shout out. We've got Diane from White Coat Captioning here with us today. Taking on all of these words, thank you very much, Diane, for being here. And that's made possible through the support of our sponsors, Netlify, NX, and New Relic, you can find the captions on the home page of LearnwithJason.dev. Jump in there, take a look and see how it goes. And each one of these is clickable. Click through, check those people out. They're doing great work to make the web more accessible, I appreciate very much. And what I want to build is if you've ever seen Link Tree, let's see if we can find where's an example of these? Can I find did they just show one? I want to see one. Link Tree, no, I want to open it, though. Linktree/shopAvery. All right, so this is what I'm talking about. So this one is kind of like if they're on Instagram, this is probably how they're using it. So they show a photo from the autumn collect and say link in bio, and you can find the autumn collection and they've got latest editions, podcasts and create your own. Apparently they're sponsored. Oh, this is fake. This is all fake. So this is a mock thing, right? But so, I want one of these but I don't want to use a service because I'm a dev. And also, I want to show my social, like I want to show that social proof, right? And in order to do that, I'm just going to do what I know how to do as a dev which is build my own thing. First and foremost, I want to start, I'm going to use Astro today because I haven't had too much a chance to play with Astro since we did the Astro V1 episode with Fred. So this was a super fun episode As TROE is kind of taking the world by storm. It's a really, really cool tool that gives a lot of it's very useful, right? It's really good. And this is Link Tree if somebody wants to get one of those without building one of your own. I want to use Astro today, so I'll drop a link here. Let's start by getting things on the page. I'm going to grab this. We're going to I'm in the right place. Create Astro, and we're going to call this LWJ links. That's fine. OK. So I want this to be just the basics. I don't know what just the basics are, so we're going to find out what those are. It says it's recommended. So that's what I want. Do I want to install the NPM dependencies? Also recommended, I'll do, yes. I'll do whatever you tell me, Astro. So, yes, also, I love eleventy, honestly, the reason I want to play with Astro, it does a lot of things I like. And I also know React better than other frameworks. And I like the idea of being able to use the component based setup, one of the things I struggle with is I don't know short codes like I know components like, if you look at the Jason.af source code, here here and here. You can poke around here. We use a ton of short codes and stuff. I've extended it, the RSS plugin and I've got a couple things, this lets me automatically move my images out of 11ty to save on bandwidth and auto process and et cetera, et cetera, et cetera. And these things like SEO images, I want to be able to filter my stuff to put that in there. I want to put footnotes because I do this goofy thing in my blog, I have these footnote things. I need markup or a short code to make that work. I'm happy to do it, it's fun to do it, but I can do it faster in React. And so, Astro is appealing to me for that reason. And I know that, also, 11ty is working on island architecture, as well, there's the is land custom component. Super cool stuff. I need to get Zach on the show to talk about that. It is really cool.

And then, the other reason, it's fun to play with new tools and this is low stakes, I'm making one page, I want to see how it feels. Actually, did I share this? Did I share the I did share that. I'm going to let that roll. Do I want to initialize new get repository? Yes, I do. How do you want to set up typescript? Strict. Oh, boy, doing it on hard mode. And now, we've got what do we have? Let's look. So we have an Astro project. Do I want to update now? I'll do it later. I don't know what that's going to do. We've got an Astro project, basic config, we've got source, it's got an oh, this is types for Astro. We've got pages, our basic page here, we've got a layout, we've got components. I'm not going to need almost any of this, which is great. So looks like we've got our style just right in here. We've got the global styles applied and then, I think the way that this will work in here is that we get scope styles in the components, single file components is something that I love. It's like one thing I really wish was easier to do in React. This is great. And then there's no way back. Will I use SSR mode? I won't today, I'm not going to do that. We're just going to do the straight to HTML. That's one of the things I like about this, we can use all of this React stuff and then just get plain HTML, so I can disable JavaScript and my thing will still work. Here's what I'm going to do. I'm going to go into my layout. I'm going to let's see, we've got the body slot. That's what we want. This is where all of our content goes. Got the title, we've got the generator. That's cool they're doing that. We've got icon, OK. So let's title Astro.props. OK, cool, we can mostly leave this intact. Looks like typography here, text colors, we mess with those a little bit. We've got the font family, system UI, that makes sense. Set the body to merging zero. I'm going to add one more here, which is to do the let's make box sizing, box so the padding doesn't add to the width of things. That always throws me off. We've got basic typography, happy, that's fine. Great. Saving that. We'll leave that layout alone. And then, we pull that layout in, and then, we can drop it in here. I'm not going to use this card. So let's delete that. We've got a welcome to Astro, we're going to say let's see, Learn with Jason links. And then, we're going to just start getting rid of stuff. Boom, get out of here. Find with Learn with Jason on the line, great. And we've got some basic styling here. We've got some basic styling here.

Heading good, text gradient, I don't care about that. I'm going to get rid of it. Pulse, we're not going to use. We can get rid of that. And all of these, we don't need either. So away they go. All right. And let's start this and see what happens. I'm going to run it with my dev. And it's going to pick up this is an Astro site. So it shows here, there's my Astro site. And then, we are up and running. Off we go. And here's our site. OK. Good, good, good. So, yeah, this is very confusing to me. They did this weird collapse thing in DS code. These are two files, but since this one is a generated thing, they've nested. Jury's still out on whether or not I like that. I feel like it's cool in theory. But I'm still digesting. Like, I only kind of get it. So in here, we've got all of this good stuff. Now Myisha, I'm sorry you're having a bad time there. But yeah, let's see. We don't need this Astro gradient either, do we? In fact, I don't know if we'll need any of that. And I'm going to start tweaking zoomed in, it's not zoomed in. I want that link to be smaller, in fact. Font size XL. Made this big, didn't they? I want this to cap at have you all seen this before? This idea of clamping? Let's just knock this down a little bit. What this clamping means, as I make the window smaller, you see how the text is getting a little bit smaller? Right? What I want to do is figure out what the right max size is. So let's let's start here. Even that feels a little bit big. Let's go with that. Is that 1.75? All right. And then, here, we want this to be do we even need this? We might be able to get away without this for this particular project. Because we're going to keep it pretty small. So I'm going to leave it out. And there's that will be that. OK. So then what I want is I want, I want these to be in a like a container, so I want that list of links that kind of look like buttons. So I'm going to index, we want let's see, how do you do this in I'm going to do it in a component like this because I understand it better. So we're going to do component and links.jsx. And then, is Astro already set up? I think I need to add something to React. Yeah, so we haven't set up React yet, so let me go to Astro and docs and we want to use React.

Just going to I can Astro add. Just Astro add React. Dope. OK. Astro add React. This is so nice. Like, I love that they do this. They show you what's about to change. OK, so you're going to go in your config file and see this difference and this difference. So now, if you wanted to do it on your own you could. You don't have to. And it's going to tell you you can run a command, that's the command I want, please run that. And it is, it is run. Now I can do a Netlify dev, and so when I go into this one, getting started. UI framework, right? So then, when we start looking at, let's see Astro components, components structure, integrations, UI frameworks, one of these. Install the integration and then, to use them you just import them, beautiful. That's exactly what I want. So I'm going to have this links component, and let's just start by saying export function links and that's going to return paragraph tag, add links and then, let's get into the index here and I'm going to import links from components links.jsx. And then, I can stick that right here and what we should see is there it is. And now we're doing React, right? And that is excellent. Got a link to the text clamping, yeah. I think jsx, clamp. I think this might be the one. Yeah, this is a good one. So this kind of walks through exactly how it works. But the general idea is that you've got the when you clamp, you've got the minimum size that it can be. You've got the size that you want it to be, which is sort of like in mid, relative to the viewport width which is 1vw and you're adding your text size, the 1 rem, and then the max that it can be. So when you shrink the window down, it'll stop shrinking when it gets to .9 rem and it'll keep growing until 2.2 rem and stop. As bigger and smaller windows, you get typographic adjustments and that is very, very cool.

So definitely check that out if you've never seen it. Oh, are they TSX files? You are correct. I need to update this one to btfx. And it's unhappy because no interface jsx intrinsic elements exist? Oh, crap, does this mean I need to I need to install types React or something. Oh, you have to reload the window. Is that right? No, that's not right. Am I going to use advanced? Probably not today. I don't think we're going to do it. Do I need I need types react, don't I? NPM IP types react, and let's see if that makes the problem go away. Where is my where are my links? There it is. OK, we needed to get the React types in. Good, happy about that. And then, what I want to do in here is I'm just going to set up a little links and let's see, we're going to need a type for this. So type links is we're going to need the label, could be a string, and the URL, which will be a string and then, I want to have a link. Then, I want my links to be an array of links. And then, each of those is going to have a label. So we'll start with a couple easy ones. We're going to go with Twitter and our URL will be Twitter.com/LWJshow. OK. And then, we're going to use these links. So we'll do a let's do a container. And then, inside of this container, we're going to do links.map link, and then we'll return going to do one of these. Link.url and link.label. OK. That's happening, I think. So that's doing what we want. What else I want to show in here? So we want to, we want to I also want to show one with Jason. And that's going to be that's going to be site, we want to show YouTube. And YouTube is, I'm going to have to look it up. It's like a nonsense string. I've got this in my clipboard, you can go and subscribe because it makes me feel good inside when you do it. I need to put this in here, we're going to copy/paste this. And we also want the Twtich. And we'll probably call that good enough. Because I don't know how much time I'm going to have to get all of these things done. How are we doing on time? We are down to 64 minutes remaining.

All right, so at this point, we've got a general list here. Jack, hold me to this. We are going to spend less than 10 minutes on CSS today. OK. And that's going to happen in we'll just do it in here. That'll be fine. So each of these links is going to be in a you know what I'm going to do is make this a nav. And then, do CSS models just work? Yes, CSS modules. Was it in here? CSS, CSS modules. So if you just import styles from OK, perfect. I'm going to do it in here. We're going to do a links.module.css. And inside of it, we're going to get me links and that's going to be a display flex, and we'll do a flex direction column, we'll do a max width of 90. But we want a width to cap at 400 pixels maybe good. And then, for each link, we want these to be displayed locked, we'll need a gap in here, a gap of 1 rem, this is a little bit of code in the dark. How good can I make it without checking along the way? I want 2 pixels solid, and let's make it, Learn with Jason has a color palate, so I'm going to peek into the code here. And I'm going to use pink dark for these. That should be good for contrast. So we're going to set the color to be this pink dark. And then, I want the border radius. Let's set it to .25 rem, I want the font size, make it a little bit bigger so 1.25rem. And then, we want padding. Let's give it padding of .5rem. And is that good? Are we happy? I think we might be happy. So let's try that. So then, what I need to do is import this. So up here, I'm going to import styles from links whoops, module.css and down here I'm going to add class name of styles.links. This is why I love CSS models, by the way. Class name of styles link. This needs to have a key. I'm going to set a key of link.url because that should be unique. And let's see what I did. So we need to fix a little bit here. So let's go back to our index and we've got our main. I'm going to set the text align to center on this. That feels OK. And I'm going to go into my links.module.css. And I should probably put these in a variable. But honestly, this is like the end of the styling once we get this done. I'm not going to overthink it. We're going to do a text decoration none. And then, we're going to set link.hover. And for accessibility, link.focus. We will set it to be background here and color of white.

There we go. All right. So that's looking OK. And then, the piece that I'm missing next, we've got our links need to be display flex, let's set a margin 0 auto. There we go. So this is going to be my little dealy. You see how that's flexing a little bit? Because the font size is still set to clamp? You know what, I think that's kind of cool. Not going to worry about it. Looks good enough for me. And time, we spent five minutes, five or six minutes on CSS. We're going to call that good. So, now, what do we do? What's next? Yeah. I mean, so OK, here's the thing. When you look at something like this, this is semantic to me. It's a nav. So we're doing navigation to any of these links. So making it a list means we have to fight with default browser styles instead of instead of doing like just a thing that makes sense. I want links and I want those links to be in a list and the browser should know it's a navigation because you're going to go to one of these pages, right? That to me is pretty cool. That's what I want. So the next thing that we want to do is I so these all work. If you click on it, it takes you to the page. That's good.

So let's go ahead and get this thing online, right? Why not? We've already got it set up. I'm going to do a GitHub repo create. I want to push, oh, wait, I need to commute everything first. Let's get add everything. So didn't commit anything I didn't need to. That's all good. Package, all of our stuff. Yeah, good. I'm going to commit all of that and say, let's see here, list o links. And now we need to create git repo create, and I'm using the GitHub CLI, which if you don't use this, it's the bee's knees. I want to push an existing local repository where in this one we're using right now. And because I want it to be in the Learn with Jason org, I'm going to include the org name, otherwise it would be created in my personal account. There we go. This is going to be social links for Learn with Jason. It's going to be a public repo. I do want to add a remote. I want it to be called origin and I do want to push those. OK. So now that's done, what I want to do is actually add this new repo. So if I go and look, this is up on the old internet. You can take a look. And oh, good call.

OK. American 2050, I've stopped using Target Blank, and the reason is I know that when I want to keep a window open, I should command click on it. And when I don't want to keep a window open, I want to navigate away because I know how to use the back button. And because that irks me, I assume it irks other people. And I hate it when I find out I've accidentally opened 15 tabs because I have a problem with too many tabs open. So this is something I'm just like, you know what, you're the user. You know how you want to live on the internet. You know, do what you'd like. And that's why I do it the way I do it. But so let's go set this thing up. I'm going to go to app.Netlify.com, I'm going to add a new site. Let's import an existing project from GitHub. I don't need Netlify stuff today, we can continue on choose Learn with Jason from the repo and wait to search my millions of I think I'm over 500 repos or something now. So let's what did I call this one? And that is going to pull up here. It's smart enough to know what's going on so I can actually just deploy this. And this will build. Actually, while we're waiting for that to build, I'm going to change the name so I remember what this is. And that is going to be what our site is actually called. So you can come back out here. We're building, built in two seconds, nice.

OK. So our site is live. And that live site 30 seconds for a fresh deploy, that's dope. And now, we've got our live URL right here. And y'all can go and check that out. So we'll be deploying additional changes as we do that. We've got 50 ish minutes on the clock. So let's get back into this. I'm going to do a couple things here. Before I start, because I'm going to have to restart once I go. I need to have my Netlify functions and my Netlify edge functions. A Netlify folder and inside of that, I'm going to create one for functions and I'm going to create one for edge functions. OK. So the next thing I want to do is I want to get into how to actually request some of these stats. And I need to just figure out, let's see, how do I what's the one we want to start with? What's one I can do quickly? I'm going to do the Twtich first. Because I have a package that will help us do that. So the first thing I'm going to do is I'm going to install the Netlify functions helpers because we're doing typescript, I need the types for Netlify functions and I also want to get this package I wrote for getting Twtich O auth which will let me log in as myself on Twtich to do some things on my own behalf here. Let's run Netlify dev, and then that is now running, and we can see up here it didn't oh, it is it did start a function server. We've got a function server running, so if I add more functions, it should theoretically pick that up. So let me go in here and let's add get Twtich stats.ts and inside of that, we're going to import the handler type. From Netlify functions. And then, I want to export, export const. Come on, handler, which is a handler. And that's going to be an async function that's going to get a request in a context, but we don't actually use it. What are you mad about? The import value and must use apparently that's a strict thing. Can y'all hear this airplane? Holy crap, it's right over the top of my house. OK. So my YouTube link, I tried to customize my YouTube link and it wouldn't let me.

It forced me to use my last name. And so, I just I don't know how to do it. So yeah, I just gave up, whatever, I'll do a short link for it. I think I actually put together like one of these. Is it this one? Apparently I didn't. I did not. So anyways, here we are, we've got our handler, and it wants to promise void, that's right, because we have to return status code, and a body. So we'll start by proving this works. Here we go, we've got our static code in our body. It shows it did reload. Which means, if I go to my running app and go to slash.Netlify get Twtich stats. There we go. Oh, go away, what are you? So we've got this output here and then, I need to get Twtich stats, right. So in order to do that, I'm going to pull in, let's see, we need to do a fetch. Crap, I need to import, I need to get node fetch. I'm going to do an NPM install node fetch to use the fetch API. Starting it, again. And I need to figure out what I did to my environment where something is weird with my CLI. I think I uninstalled and reinstalled node managers where it uses the wrong version of wrong things, one is from N and NVM, and it's confusing to me. Some day, I'm just going to burn this computer down and start over, again. But OK. We've got, the thing is running, it's doing what we want. I'm going to make this smaller. And I'm going to make this smaller so that we can see them side by side and try things as we go. And I'm going to collapse the sidebar because we don't need it anymore. And I'm going to import fetch from node fetch, and then I'm also going to import this get Twtich access token from my Twtich Oauth library. And what I need to do first and foremost is I need to figure out where to go to get Twtich API stuff. I have another project I've done that already has this. So I'm using that URL as a cheat sheet here. So the Twtich API lives at HTTPS. And I'm setting it as a URL because it lets me kind of break this stuff out. So I set up path name. And the path name for getting your user follows is helix users follows. Right? And then, what I need to set is some search params. We've got search params and I'm going to do a set. We need to set the 2 ID, that needs to be my Twtich username or channel ID, I guess.

We'll have to go find that. And then I also need to set search param for what set I don't know why it does this. This is like Twtich requires you to do requires you to do a limit of the number of results you get back even when you're asking for a single thing. I guess you're not, you're asking for users, so it's going to give me an array of users. And I don't know why passing a single anyways, API design is hard. So now that I've got that, I can actually make a request for it. So let's do one of these. I'm going to get, wait, get Twtich access token. And now, in order to do this, I have to set my Twtich client ID in secret. And because those are environment variables, I want to set them in here. I want to go into my site settings and down to environment variables. Let's make this bigger so we can see what's going on. I'm going to use this beta. I've enabled this by going to Netlify labs and you can turn on some of the experimental features. So I'm going to opt in. What I want to do next is create a variable. And so the first one I need is Twtich client ID. And I want this only to be available in certain scopes.

Like, I don't really need the build to know about this. I only need my functions to build about this, to be aware of this. So I can turn all of that other stuff off. And if I wanted to, I could run this with a test user in production, deployed previous branch deploys, et cetera, et cetera. But in my case, I want it to be the same everywhere. So let me pull up, stored somewhere else off screen. And I'm going to set this to be hidden. So let me go get this actual value here. Just a moment to find that information. I'm going to get my environment variables out of that. I need that Twtich channel ID. So we can drop that one in. And I'm going to create it. I need to do the same thing for my Twtich oh, no, that was the wrong thing. I just put the ID in. Crap. So we're going to do the Twtich channel ID. That, again, is going to be available only for functions. Don't trust the hackers with anything. I need to edit that, you're still hidden. Did you see me peek like that wouldn't totally expose the key if it was visible. And then, I'm going to get the client ID. That's what actually needs to go in here. Save that. And then, I need one more, which is the client secret. Tuck that one up. Add another one, this is going to be Twtich, client secret. And again, only needed in function. And we want the same value, that's all hidden. Now, I can go get my client secret and drop that in. And I'm going to create that variable. So this now, theoretically should let me pull all of those details in, meaning, I can in here do my process, process.n.Twtich channel ID. Do I need to install type nodes? I do need to install types node.

OK. Go out here, do a refresh. We don't get an error and this why won't you go? Argument of type, string undefined is not assignable to parameter of type string. OK. I guess we'll just do that thing that you do when you want typescript to leave you alone. Because I know that exists, but typescript doesn't know that exists. Theoretically, I should probably be throwing an error if that doesn't exist, but I'm not going to stress about it just because I know that I've set it. And we have our get Twtich access token, which is here. And that should allow me to allow me to get data from Twtich. I want to load my Twtich data. Twtich response, and that's going to be fetch, this is the fetch we imported up here, the API we imported. And we need to pass in string and pass in detail. I'm using a little bit of a cheat sheet here so we don't have to look up the docs on how to actually load these things, I don't want to focus on that. The way that Twtich works, you have to send it your client ID, which we've put in Twtich client ID. And then, we need to send an authorization. And that's going to be a bearer token, which is the res access token that we get back from here. Right? So that sends back an access token, which we can see the Twtich token response. And that auto completed for me because I did, apparently, remember to add types.

So good deal. Good deal. Good deal. Why don't you like this? String undefined. Oh, again with the thing. Let's do this, actually. Let's do if process.m.Twtich client ID, then we're going to return status code what is 401 and a body of must supply valid credentials. For Twtich. Now it's not mad anymore because it knows if this is undefined we've already failed. So now I've got my Twtich response, we can check if it worked. So we'll do an if Twtich res OK. Actually, I want it to be if it's not OK. And then, we will return actually, you know, I want to it's fetch, channel, fetch Twtich stats and then we can do a Twtich res status, which will give us the unauthorize in the console. Down here, I want to get the data. So we're going to do data and that's going to be an await Twtich res.JSON. And in here, I want to get the followers. So let's start by console logging data so we can look at it. And what we should see is when we go out here and I read, must supply valid credentials for Twtich.

Because we haven't stopped and restarted since I added the environment variables. Now that I added those. Oh, I didn't link this project. That's what went wrong. Let me get over into here. We'll go to general. If you're using this Netlify link site ID, and drop that in. And if I drop that in, Netlify dev now. The environment variables. Now that we're linked to the site. I'm logged into Netlify, and then if I come back out here and run this command, gives me a boop. And we get data. So we've got a total of followers. And then, oh I see why this OK, OK, OK. So, what this API is actually returning. This is why you do a limit of one. It's returning the actual user accounts that are following and then you get a total up at the top of like total number of followers. That makes OK, I didn't understand that. And that makes a lot of sense. So what we can do instead is in here, I want to return const followers equals data total and then, we can zero. Let's not do that. We're going to type this. Type unknown. So that's going to mean that we need to define what the subject is. Let's do that. We can do it as a type. And we'll say Twtich followers, follows response. And that's going to have the total, which is the one we just got back, which is a number. Is that good enough if I do it as set this as Twtich follows response. We get that, and then, I can return my number of followers, which I think we want to do is send it back as some JSON, we'll stringify and say like, say like site Twtich and number of followers. And then you want to send that number. So you can just send that right in. And this, I think, should mean here we go. So from Twtich, we get stats of followers and now we've got the number of Twtich followers.

So that's good. This is going to have to run every single time. I'm going to set a ttl on this. I'm going to pull in, oh, wait. That is a Netlify functions like so. So this is a type, this is a function or a helper. And so, I need to wrap this whole thing. So I could do this but I don't want to. So what I'm going to do, instead, is I'm going to take this piece out. And what I'm going to do is say function, get Twtich follows data. And it's a function. And what I'm going to do down here is export const handler handler. And that's going to equal builder of get Twtich follows data, which is this one up here. So what I'm doing is I'm turning this into a builder. But then, what that means for this to work is I want to also send back a TTL. What that means is I can decide how long this thing lives. And I'm going to give it an hour. So this is in seconds. So now, what will happen, this will actually get cached for an hour because we wrapped it in a builder and added a TTL. When you call this response, it will the first time run the serverless function and for the next hour, it'll just return the cache response, so cuts down on your usage and gives you a bunch of details. And yeah. Yeah, yeah, yeah. So wait, node got fetch integrated in '18, that's so dope. I should totally do that. Thank you for the sub. And yeah. Yeah, yeah. Yeah, Jack, you're probably right, all of my dot files need to go away. All right.

So now that we've got this, it still runs as expected and oh, thank you for whoever just subscribed. See, that number goes up. And it makes me feel good. So yeah, it still makes the fetch request, but now it'll be cached. So if we actually deploy this, I can get add, get commit and say feature add Twtich stats. Then we can push, this will build nice and quick. How are we doing on time? We've got about 30 minutes left. So I want to get the you know what, let's do just the Twtich stats instead of trying to get more stats in from others. I want to make sure we get this running. And that will give us the general idea, and then, I can add the rest of these. It's going to be the same process for each one. We've got to go get the API keys, send a request from a serverless function, we'll cache that response for whatever amount of time we want it to be using the builder. And then, return that all to the edge function. So we're going to request it from the edge function and update those stats there. So this let's see, are you done yet? You done yet? You're done. So if we go back out here and is it this one? Yeah. So now, I can go to .Netlify functions, get Twtich stats. And this first time I don't think I came back super fast and came back with a response headers, where is it? Somewhere in here, it should show it was cached. H0, oh, the age is in minutes. Age 1. Now you can see it's responding with cache data. The age is over 1 minute and when it gets to 60, it'll pull, again. So that's the gist of what we're doing here. We're trying to make these I don't want to get rate limited by any of these APIs, but I do want to pull that later data. And so, yeah. Nathan a follower is not a subscriber. A subscriber is somebody who is actually paying. And my subscriber account is in the dozens. I do not make very much money from Twtich. Followers are just people who get notified when I go live or you can even turn that off if you want. You can turn off the notification. Followers are people who have expressed interest in my content since I started 300 and something episodes ago. I have no idea how many of those people know if or when I'm live. But yeah, no, definitely not. Definitely not making that kind of Twtich sub money. I'm not Twtich partner.

But now that we've got this, what I want to do is go back to the site in the front here. And I want to I need to start the server, again. I want to get that Twtich follower account into the site, but I don't want to do it on the client side. Because if I was on the client side, I would write JavaScript function, once you loaded the page, I would go off and hit that serverless function, grab the data and put it into in here. Under the thing. That would be fine, I want this to be snappy and fast. I don't want that client side JavaScript execution every time. And as I add more social networks, that time to execute will get longer and longer. What I want to do is do that in an edge function. I want to create an edge function and we're going to call this add stats.ts. I want to go into how do we want to do this? I'm going to export default async function. And then the function is going to get a request, close this down, again. Maybe make this a little bigger since we're going to write a lot of code. This in typescript, a request is like a built in type if you're using node. And then, context comes from Netlify, so I need to get content from Netlify. So that's going to be an error for a second while I get that part done. We want to import context and the way that works is that we import from HTTPS and I remember this off the top of my head. This is never used as a value. Right, right, right. And it says, cannot find module. Yep, we've got to do a thing. So when you're using edge functions, you're in Dino. So we have a recipe for that in the CLI, so I'm going to run Netlify recipe VSCode. And it'll add that.vs code folder to update the settings and give me the right pieces for edge functions and Dino and all of that stuff. When I come back out here, now it knows, it's not yelling at me anymore. From here, what I can do is I want to load the response. When you run an edge function, it's kind of like middleware, so I'm making requests to my links page. What's happening under the hood, the request has to go through the CDN, which then checks to see if it has the asset or the origin server, the origin server has to find that asset, deliver it, return it, and we have to send the request back. So your request kind of goes to a server or a CDN or whatever it is and says, I would like a thing. And using the headers, URL, all the details you send, the wherever you've requested it from will say, great, here's the asset you requested and send back a response. Requests/response, they've got standard APIs. What we do in middleware is look at the incoming request so we could say, oh, you don't have an authorization header set, I'm going to redirect you to the log in page. That's one thing you can do. Middleware. But the other cool thing we can do is look at the returned response and say, OK, what this page returned was this, but based on your headers or your location or whatever it is, I can also transform that response. And this is what I think is really cool. That's the piece we're going to use. What I want to do is get that response into memory. And the way you do that in Netlify, we're going to await context.next. And what this does, it pulls whatever the next link in the chain is. In our case, is the response. So I want that response.

And then, what I want to do next is I want to transform this response. So to start, let's rewrite some text and so I'm going to use a tool that I love if you watched the show before, you may have seen me use this. It's called HTML re writer. It's super cool. And I'm going to copy/paste this thing because it's kind of long. But this comes in from worker tools HTML rewriter. And it was a tool originally built by Cloudflare. This allows it to work on Dino. And now what I can do with this is I can set up an API for it. Actually, the way that it works as a no op. You set up the HTML writer and run on response. And so if I set it up like that, and we just run it and then I don't know we're going to use the request today. So I'm going to add this underscore to mark it to the ID is not being used. So now that I've done this, what we should see in here is when I start the server, it'll say that oh, no, it won't. I haven't set this up yet. I need to go into my Netlify.toml, which we don't have yet. So let me create that. And I'm going to set up edge functions and on a path of the home page, I want to run the function add stats. That's my edge function config, and now it says it's loaded the edge function, add stats. And I believe it'll just work and what we should see if that's the case is I'm going to go to add stats. Why don't we console log something? All right. Now, when I refresh this, didn't pick it up. I think I need to restart because I changed my Netlify.toml. And when I go back to here and reload the page click the link instead of reloading the page. There you go. All right. Now our edge function is working, that's our no op, right. It doesn't do anything yet because we haven't told it to do anything yet. We didn't tell it what to transform. What I can do is I can go in here and I set a let's see, on here we've got the nav, we've got the links. Why don't we set something on the just for the sake of ease here, let's set the index. I'm going to set this H1 with a class name of rewrite test.

OK. And then I can why are you unhappy? Oh, I'm in Astro. There we go. So that's a JSX to Astro thing that's going to trip me up forever because my brain won't do that. But now that I've got this. What I can do is I can do, you get regular CSS selector, so I do a rewrite test and then I can just do stuff with it. And are edge functions production ready? Yeah, you can use them now, they're dope. There are several companies that are very large using them in production right now, which I enjoy quite a bit. So let's set this thing up. So what I'm going to do is I'm going to run this set of operations on whatever match this is. We've added a class of rewrite test, right? It's here. So I want to do some stuff to any elements that match that. I'm going to use the element API, and that gives me an element, which will be the type element, which is what I was bringing in from here. It's going to yell at me. Import type. Implement. I need to grab this and copy/paste it. OK. And this is a function. I'm going to just get that element and we're going to do something to it. I'm going to do an element. we don't want to append. Let's set the inner content to magic. All right. Now what I'm doing is I'm updating. And now, if you look at this, though, check this out. Here's what's really cool about this type of set up. What am I doing? That's the shortcut. If I go in here and I hit, I think it's command shift P, I can disable JavaScript. OK. So JavaScript is disabled now. Note that this still worked. JavaScript is off, no JavaScript is loaded, and it's still doing that replacement. So this is what's so freaking cool about this. You don't need JavaScript to make this work.

I absolutely love this feature. It really, really makes me happy. So in looking at this, we have the ability to now like do some replacement. And I want to do the replacement using our Twtich stats. So I'm going to get the stats. And we're going to start because it's we're just going to use this stuff right now, I'm going to do what's my API URL? It's going to be can I do like a it won't work from just local. I'm going to have to do a thing here. So let's do I'm going to hard code the local for a second. And then we'll hard code to the public one when it goes. So then, it's going to be /Netlify functions get Twtich stats, right? And then, we need to to get Twtich stats. Going to be wait stats.json. We're going to assume that works because we're chaos agents. And down here, let's just console log it. We'll turn that part off. And now, we can run this. Are you reloading? Or did you crash for real? You're reloading. Takes a second and I'm always just a little bit faster than I should be. Here we go, we've got our data loading in edge function, that you think to the two additional people who followed. Man, that is cool. I should never look at these numbers, my Dopamine. So I want Twtich. I'm going rename this, it'll be Twtich stats.stats. I want, instead, let's get Twtich, and we're going to delete this part. What are you doing? Stats. I'm going to use a data attribute. We're going to do data, data in the links thing? A key and we've got a link URL, styles link and then we've got a label. And so for the label, we want to kind of slugify that you know what, we're going to hard code this thing. That is not something I want to burn time on. We are short on time. How many minutes? We've got like 18 minutes. For each of these I'm going to hard code in a network slug. We'll call this Learn with Jason and call this one YouTube, and we'll call this one Twtich. OK?

And then, that's not on the thingy, I've got to add it to the thingy. And now it's happy. And then, in here what I'm going to do is each of these is going to get a data network, data site, and we're going to feed in the link. So now that is set up, I can select on the one I want to select on. So I'm going to go into my add stats, and we're going to do data site, and I want Twtich. So on data.Twtich, I'm going to element. in this case, I want to append, right? And this is going to let me do an HTML element. And then, I'm going to set it to be HTML true because I want it to actually parse that HTML. So what I'm going to insert here is a span with a class of stats. And then, inside of that, I'm going to put Twtich stats followers. And we're going to close this off. And reload and assuming I didn't screw anything up, I did screw something up, what was it? You can append, no need to replace the whole thing. Typo, where's my typo? Oh, here. OK. So now we've got our Twtich follower account appended here and that makes me happy. And so, let's go and we can do a stats how does that work? Let me do a display block, see if that works. And it does. OK. What I can do is set the font size to be O 0.875rem. And yeah. OK, great.

And then, in here, I want to do two more things in the time we have left. I want to format this number, and I want to add the remaining little bit of details that we need here. Which is, I want to have the followers and then I want to format this. I'm going to do the intl library. So we'll call it formatted and say new oh wait, how does this work? Intl here. So what I want is a number format, right? You see how this is picking up different styles based on where you are? So I want to you get the formatter and .format your number. That's fine, we can do it that way. So we are going to number format. And then we're going to do US, that's where I am. And we're going to pass in a few things, I don't want currency, I just want numbers. So options don't want currency, don't want currency display. Locale matcher notation, maybe it'll work if you only put in that one little piece? Let's see, notation. Short form, I like short form. Let's try that. I'm going to set it up to use it was I just looked at this, notation, typescript. And then, what do we want? We want, let's see, do we need to add anything with this? Compact, what else is in here? Rounding parity trailing zero display, let's just try this and see what it does. So I'm going to stick this in instead. Oh, wait, this is the formatter. So we actually need to format Twtich stats followers. And then, I can do this. It works theoretically, let's try it here.

11K followers. No, that's a lot of that's a pretty generous rounding. So I want to a rounding thing. The minimum number of fraction digits to use, the maximum of fractions to use. Default is the larger minimum is zero. OK, so I want I think this is what I want because I think that'll give me minimum can be zero if it's like actually a round number, and maximum, is this going to autocomplete for me? Maximum, we'll do like a one, like a 10.5 is fine. So let's try that, again. There we go. 10.5. So that is what I'm after. That's what I want, and that is kind of what we were out to do today. So to clean this up, what I'll continue to do here is, I will, you know, find the logos and I'll go out and get the API calls for each one of these services or like in the case of Learn with Jason, I have an API for it that is API episodes. And I don't know if this one actually gives you a total count. I feel like that one might not. There's probably something I need to do to make that one work. But whatever it is, I can go figure that out. And then, this will be a nice little thing I can stick into my bio that kind of shows where you can find me online, I'll style this up a little bit so it's got a logo and uses my brand colors a little better. Oh, deploy it, that's a good call. Let me deploy.

Get add and then I'm going to get our settings. Going to do commit and say feature display Twtich stats push. So that is going to go into our deploys here. So we'll follow that along. Shouldn't take too long. International number format rules? Any questions, chat, while we're doing this? How are y'all feeling? Finish processing, site is live. Let's go back to the home page. Uh oh. Are you not done? You say you're published and it's failing and it's failing because let's go to the edge functions. And it says oh. See I knew this was going to happen. I did that thinking to myself I'll fix that before I go and then, I didn't. Let's go in here, fix that.

Save and get commit, fix, prod URL. Push. Now, we wait another 30 seconds. Call it from dot end. Yeah, I could probably I need to look into this we can probably get that URL dynamically. Let's make sure this works and then yeah, can I get that from a default? I think I can. So let me check once we've done this. I'm going to give it a shot and see what happens. Are you done? Published, deploy. Published. Now, back again. And it hits the thing. And hot diggity, nice and snappy, makes me happy. And we get our thing. And again, if I go in here and I disable JavaScript and I run this, like we still get that count and it's really fast and we're not loading anything and actually, we can go even a step further here and let's look at the actual code. So this is what's generated. This is what's coming out of Astro. And this is one of the things I think is really cool here is that note there's no script anywhere. We're not injecting any JavaScript of any sort. So we've got our style sheet, we've got our Astro classes. Here's the H1, the nav we set up and when we open this up, this is in the source. If I view source on here, we can actually see there's no JavaScript enacted here. And you can see all of the content. It's not formatted very well, but you know, it doesn't need to be superhuman readable. And we've got all of these pieces and down here where the Twtich stats are, we've got Twtich and breaks and gives us this 10.5 K followers. This is delivered in the final HTML as far as the browser is concerned. The fact that we injected it with an edge function makes it into static content as far as the browser is concerned. And that's what's so dang cool, right? That's really, really fun.

So let's see, we've got how many minutes left? We'll call it 3 minutes. Let's see if we can get the URL coming out of a let's figure out what's available to us. And just try, let's just console log it. And see what happens. I'm going to console log. And in Dino, you get your environment variables and I'm going to see what's in URL. So let's try that. Finish reloading. Oh, wait, wait, wait. I did it backwards. Now that's reloaded. I'm going to refresh. And we get undefined, OK. So let's see if I fall back to maybe it's just for a local host, host link. And we refresh, we get local host. So let's try let's try this and see what happens. I'm going to stick this in here. Actually, you know what I'm going to do, I'm going to do const API URL because I don't know if we get a trailing slash or not, and this will fix that. So we can stick this in here. And then, we can do API URL .pathname, and put it in here. And then we can take our API URL 2 string. What that will do is just make sure we don't end up with duplicated slashes or something like that. So this should continue to work, it does. Now I'm going to nope, not dev. I'm going to check that get net everything, and we're going to say feature, dynamically find URL. Let's see if that works. Will this be faster than using JS with the client? Yes. So what this means is that when you make this request, the edge functions run in the CDN. So when you make the request, you're hitting the CDN network and the edge network roughly where you are.

It's much closer to you than an origin server and stuff. So what we're doing, if you're the first person to hit it, it might be a little bit slower because you're going to hit the edge function which needs to call the serverless function and make that request and brings it back to you. But it's still going to be really fast. And if you are a follow up user, it's going to be the edge function hits the CDN to get that cached response from the serverless function and returns it back to you. It should be rip roarin fast. So let's look at our build here that's published. Let's get in here. Are you going to blow up? Yeah, it blew up. Uncaught exception. It's still local. So URL is apparently not set, which means I need to find out what the actual URL is, which I don't have time to do, but we can figure that out.

So in what I would do to go and fix this, which for now I'm going to just reset that to be our deployed one here. In here and we'll put in here, as well. So this will allow us to fix it. To get URL from int. That's a thing we will have to figure out because it is possible, I feel like I've seen it done. I can't remember how to do it off the top of my head. Fix use prod URL and then off we go. And so, that will work everywhere. It just wouldn't work if you deployed the edge function as a deployed preview. It would still be calling the production get Twtich stats. So that's obviously not the ideal.

You could manually maintain what your URLs are. But I know we provide it. I don't remember exactly where. With that being said, y'all, this is well, so the deployed prime URL I'm pretty sure would give you the site's like production URL. Anyways, got to go figure that all out. Let's make sure that works. Great. And with that, we are going to call this episode a success. You can find the code for this on GitHub here. And you can find the demo itself at LWJ link tree Netlify app. And this episode like every episode has been live captioned we have Dawn from White Coat Captioning, that is made possible through our sponsors, Netlify, New Relic, making this more accessible to more people. We've got fun stuff coming up on the show. We've got coming up on the 27th. I'm taking two weeks away from Learn with Jason, I have a company All Hands and taking the first actual vacation I've taken in a long time. I'm going to take a week off and sit on a beach and be quiet. That's going to be awesome. We're going to join up with Tweed. This shared element transition API is so dope, make sure you add on calendar, follow on Twtich, subscribe on YouTube, whatever it is. Don't miss these episodes.

This is going to be a custom media player like play video from wherever. This is going to be shared element transition stuff. This is super cool. This web auth stuff, how can you use your fingerprint on your keyboard to log into a website.

Like, these are going to be really, really cool episodes. I'm very, very excited. We've got even more coming that's not on the list just yet. So make sure you get subscribed. Put it on your calendar and you can see what's coming. With that, we're going to call this one a success. Let's find somebody to raid. As always, thank you very much, chat, we will see you next time.

Closed captioning and more are made possible by our sponsors: