Build and Deploy a React App from a Monorepo
Managing large, complex, and/or multi-team codebases can be simplified by using a monorepo approach. In this episode, Juri Strumpflohner will teach us how to use Nx to build and deploy React apps.
Links & Resources
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 LENGSTORF: Hello, everyone, and welcome to another episode of Learn With Jason. Today, we have Juri Strumpflohner. How are you doing?
JURI STRUMPFLOHNER: Doing great. Thanks for having me.
JASON LENGSTORF: I'm superexcited about this one. I'll talk about why, after we take a moment to talk about you. Do you want to give us a little bit of background on yourself?
My name is Juri, I'm from Northern Italy. I'm in developer experience. I'm excited to jump on the show.
JASON LENGSTORF: Yeah. A lot of people, as they work in bigger companies and on more complex projects, they find themselves in this really challenging spot where we have a ton of code, we have a ton of teams and it's just painful to try to work together, right. Like, from my own personal experience, I've been on teams where each team has their own set of repos and in order to launch, I have to go across each team and say, hey, we're going to launch. Have you run your test. You're returning into merge conflicts and you've got a lot of equallyfrustrating complexity around all this.
So, this is a scenario where you have a lot of expertise. And, chat, are you using a monorepo at work? Have you used them before? What's your general sense of them.
Juri, if you want to talk a little bit about monorepos in the abstract. [Laughter]. Right? Like, how are we managing code at scale?
The main thing, when you start thinking about a monorepo, usually� as you already said� when you want to try to help teams develop together in a more optimal way. As you said, you have different repos, repositories, teams working more separate. There are some pieces where you need to collaborate. Right. And it could also be that one team just, for instance, handles the UI design component thing. That is one of the common things you see, someone handling the UI design guides and they work isolated in their own repository and publish versions. Sometimes if you can, you publish to the open, if you cannot, it gets more complicated. You have to set up internal cloud registers and it gets much more enterprisey. What we do with our clients, when we work with them, we help them set up such monorepos. You circumvent a lot of those isolations and teams that work on design guidelines, they can try out and experiment very quickly. It's all about creating a branch, testing out a couple things and see how it works in different apps.
JASON LENGSTORF: Gotcha. Yeah, that totally makes sense. Chat, are you getting a lot of audio blowback from me? I made a bunch of audio changes because I was issues before. So I've rewired everything�
JURI STRUMPFLOHNER: I hear you good.
JASON LENGSTORF: I'm just going to throw some headphones on and avoid this problem all together. [Laughter]. How about now?
JURI STRUMPFLOHNER: I hear you good.
JASON LENGSTORF: If anybody hears any echo, just let me know. Good, good, good. All right. Your point about, like, the� the ability to iterate and experiment quickly, that also brings up something that I have run into a lot, which is, like, I have this, like, this tension that I constantly feel around, when I'm working on a project, there's a piece I feel would be useful if I publish it as a package. For example, I have this social image generator and it's just a little piece of code, publish it on NPM. I made another site and I want to use that code, I want to build it. And I'm like, so now I have to go to my other repo and I'm pushing an alpha version and I screw up my NPM launches and bumping, patch, patch, patch, oh, crap, I just broke everything. And it would be avoided if I could work late in the code. Maybe I should copy/paste the code between projects so it's easier for me to iterate on.
Me, one person. So, is this something that� would you reach for a monorepo to solve those types of problems, as well? Like, how do you apply monorepos in your own dev?
JURI STRUMPFLOHNER: The thing about monorepos, people always think they have to be huge, right. Many immediately say, I'm not having that case, I'm not Google, I'm not Microsoft, which are known for having gigantic monorepos. A monorepo can be� you have an opensource library, would you want to split up in multiple packages. It makes sense to have them in the same repository.
You have four different libraries, the core library and different extensions if you have a new UI type of library. So, you already have a monorepo in that sense. Right. So, they can be pretty small, as well. Even if you start with one application. People start with one application, they split it up into small libraries. It makes sense, it is easy because we're already in the monorepo or we have that environment that allows us to quickly add new application and link to existing libraries.
You don't have to start huge. You can have one library, one demo application, publish it to GitHub, easy to maintain. Over time, naturally what happens, you will add [Indiscernible] ones. You'll add, like, new ones. Sometimes people [audio cutting out] different library, but then, that would mean I need to set up a new repository, a new CI, so they don't do it. So that's also one thing that you also see.
Even in companies, the clients I work with, in terms of the monorepo area, it's not that they have one, gigantic repository. Some of them can get quite huge, but they can have multiple ones, in the sense of per domain or suborganization that might exist in those larger, Fortune 500 companies. You can have a monorepo and then one library in there, that utility one, you want a general repository because not everyone is in the same repo, but others may want to benefit. It's not that you cannot do it. Start small and then scale as you see, like, you need more. And on an asneeded basis.
JASON LENGSTORF: Yeah. I mean, this is, like� I like the idea of making it flexible and, you know, I recognize that there's no onesizefitsall solution when it comes to code organization. It's very much organizationallydependent, teamdependent. Are there things you have seen very clearly, whenever teams adopt this strategy or they stop doing this thing, it makes a night and day difference in the way that they manage their code?
JURI STRUMPFLOHNER: Yeah, I think the main benefit, which we see coming out of this whole monorepo approach is the collaboration aspect. A monorepo, like, although you could have your repository and merge them in one Git repo. You would have a monorepo in the sense of one Git repository, but it wouldn't give you any benefits. The benefits start happening once you start sharing code among teams.
You create� because that's what a merge usually is. The lowerlevel libraries that are more utility, authentication, those are shareable. Once you are in that situation where you can share, that will evolve. You also need to put guardrails in place. If you jump into monorepo scenario, yeah, let's just throw everything in one repository and let people go and do their work, that's not how that happens, right? Monorepos can help facilitate communication and share code, but you still need to do some additional work on the side. It's not full [Indiscernible] completely.
JASON LENGSTORF: Yeah. And so, this is where we can kind of transition in talking a little bit about a specific tool, which is Nx, that you work on. When we start talking about the structure of a monorepo and thought just chucking all your code into a single repo, which guardrails am I setting up on my own, versus which guardrails can I rely on the tools to set up for me?
JURI STRUMPFLOHNER: If we talk specifically about Nx, it is pretty flexible in the sense that you can have multiple packages in there that you might be more accustomed to if you come from the learner world. And that's an approach which we see a lot of people using [Indiscernible] opensource repositories. You have different packages you want to publish. There might be dependencies between those packages, naturally, but nothing more. You might have a demo app or something alongside those.
An approach we adopt a lot is more a division about, like, applications and libraries. And so what it means is, basically, you could think about them, like, having your usual� I don't know� create React setup, where you have the application or your Angular application, you divide them into subfolders, per feature, domain, per team. Whatever. You need some sort of separation, right. In a repo, we recommend, not just at that point, not to keep them in a folder within the same application, but create a workspace library. The library that lives in the monorepo. But it is an individual piece, like split out of the application. It doesn't mean others need to consume it. It gives you more benefits in having team more focused on a smaller piece and having a public API structure where it can consume certain pieces. That's usually the structure which we recommend.
That gives you a guardrail, the app should be as thin as possible. It's like a deployment issue containment one. Logic, and the stuff, happens in the [Indiscernible]. And then, as you start, like, that's perfectly fine. And usually, you're good to go. And as new applications come in, you need to start to think about, okay, which apps can import which [Indiscernible]. It might be consumed by one application rather than another application. At that point, you need a tool that supports you with that. That puts those guardrails in place. Especially new developers so they don't accidentally pull in NPM packages and libraries.
JASON LENGSTORF: Thinking about this from a practical application standpoint, if we run through a� let's call it� just a hypothetical scenario. So, I'm running� let's say I've got my company and my company has a public package that we've deployed and it's a utility and we have a demo site that shows the docs and we have our company site, which is what's on the use of that library in the first place.
So, I would have� in my lib folder, the NPM package that I'm publishing and two apps. The, like, package demo site and my company site and each of those apps would� I would have some kind of, like, quality check in place. So, let's say my lib is something that can be unittested. I can go through and have my company app test to make sure that whatever changes I make in the library don't break my app and the demo site, we have all the various use cases that we want to run through and that's kind of a [Indiscernible] test making sure my library does what I say it does.
When I'm working on any one of these� so, I want to make some changes to my library, does Nx has the automation in place so I can say, okay, I've got a change ready for my library, run a test against everything that consumes it and tell me if I broke things?
JURI STRUMPFLOHNER: That is one of the vital pieces you need to have. That's a problem I mentioned initially, if you throw libs and apps into single Git repository, but you don't have the tooling in place, usually, you'll be fine and nice. As you grow, you regret your choice for a monorepo and you will back out again. That's something which Nx, for instance, helps with. It builds up a project graph so it is able to read the source code and understand, like, okay App 1, the company site. As well as the doc side pulls in that library via the import statements. It builds up that dependency graph and that is kind of the core structure that is used for a variety of different optimization strategies.
One of them is where you can say, okay, build whatever got affected. Yeah, you changed that lib down there and based on the project graph, I know those two other applications depend on it so I need to run tests or build or endtoend tests. So, yeah, that's vital. [Indiscernible] skyrocket.
JASON LENGSTORF: Yeah, exactly, right. That's what I worry about when you start looking at monorepos. Thinking about, like, various experiences I've had on this. You know� when I was working at IBM, we had a lot of shared tools, but we didn't have the structure of shared tests and making sure that things work so there was this very manual and timeconsuming process that whenever you touched a library that affected someone's code, you had to open a PR that ran that thing and if they didn't have tests, you had to run manual QA. There were a lot of really complex steps. If you didn't do it all in one day, half of your day [Indiscernible] because they push changes tomorrow. It was very, like, urgent [audio cutting out] and stressful and timeconsuming process. It's got this dependency, you make it less of a human, you have to remember to go check every repo that's affected, which means you have to figure out who's using this repo, which is a huge problem when you have dozens of teams in a company. Or even, just, like, four. [Laughter]. Two or three teams that use a thing that you build and you don't work with that team, you have no idea if they installed it or uninstalled it or if they forked it. There's all this little stuff that you have to go deal with, it's a big pain.
JURI STRUMPFLOHNER: You don't know which version you're consuming, right? It's all about shortening that hold feedback loop. If you make that change, you push it out and your PRs run through. You immediately see, okay, app 1, 2 or 3 are breaking and maybe you're able to figure it out. Oftentimes, you're able to figure out public API changed that or I need to fix the consumer code or reach out to those teams that work on those apps, right?
The contrary problem would be you publish a release candidate, maybe you have an internal mailing list and you hope they're going to test it. They release a public version, but only half the team does it or they upgrade two months from now and their app is breaking. That feedback loop can be really, really long rather than being instant, as you make the change.
JASON LENGSTORF: Yeah. You know, talking about this reminded me of what I think� because I've had� we've talked about Nx on this show before and one of the moments that I think really blew my mind, talking about Nx, was when we started talking about private packages because this is something that is� every company that I've ever worked in, it's a huge pain. You have a private package and you have to figure out package management, where does it live, how do you protect it and all these keys and anyone that wants to use it, you have to walk them through updating their NPM config or a file on their local machine that has the right username/password. It's so much complex work that has to be done to let somebody do a local dev. And I know, personally, I have lost developer days to this. Like, fullyblown an entire day trying to hunt down the right person who has the access token. Something that Nx does that I think is really, really cool is, if I put a private package as a lib, in my monorepo, I can install it in my apps, like an NPM package. I do @company/packagename. But I don't have to publish it. I can pull it out of that libs folder so our monorepo becomes our private package manager and, holy Christmas, does that make a huge difference in the level of complexity involved with sharing that private, internal code.
JURI STRUMPFLOHNER: Yeah, yeah. Yeah, absolutely. There's no� actually, if you live in that monorepo, like, in that case with Nx, or alternative solutions that have the same approach where you directly consume the library list, if you [Indiscernible] or NPM workspace, in that moment, they really install it into Node modules so you have a link that points to those libraries. In Nx, you can totally have that as well. It directly used TypeScript and [Indiscernible], something NPM scope, usually like your workspace name, slash library name and it points to a folder. So there's no publishing going on. Which obviously, makes the whole process a lot easier.
JASON LENGSTORF: Yeah. So, I think, to make sure that we leave enough time to do some coding here, to kind of put� to maybe to try to take everything we talked about and put a little bow on it, when it comes to monorepos, your guidance is� they're very helpful no matter what scale you're at, but you do have to do the work, up front, to make sure that you organize things well and add the right tests and guardrails to identify when your changes affect things.
JURI STRUMPFLOHNER: Exactly. Nx helps you with that. Nx has features that allows you to put up those guardrails or automates that and the process of generating, the process of generating library. But, yeah, totally. You need to have some things in place. Either tooling or something like that, Nx for instance, that guides you along. It's not going to be a nightmare a couple months' in.
JASON LENGSTORF: Gotcha. That's probably a good transition point for us. Why don't we jump over into our pair programming view, which I will do right now...and, check out this new background. I'm very happy with this. You've got the transparent terminal. Like, ooohhhhh, neat.
Let me start by doing a quick shout to our captioning, which is happening right now. I didn't have the window open. We have Vanessa with us today, from White Coat Captioning, who is taking down all these words to make this show more accessible. That is very much appreciated and made possible through the support of our sponsors, so, Netlify, Nx, thank you very much. And Backlight. So we are talking to Juri Strumpflohner today, who you can follow @Juristr, on Twitter, drop that into the chat...if you want to follow along with the captions, they're on the home page of learnwithjason.dev.
We're talking about Nx Dev, right? Boy, am I glad I got it right on the first try. [Laughter]. And I believe that is the extent of the things I know about what we're doing today. So, I will now ask you for guidance, as we continue forward here.
JURI STRUMPFLOHNER: Yeah, sure. I would say, we can start creating a React application, a React monorepo.
JASON LENGSTORF: Okay. So, let me make a directory. I'm going to call this "React Monorepo Nx." So, I'm in a completely empty folder here.
JURI STRUMPFLOHNER: Cool.
JASON LENGSTORF: Ready to roll.
JURI STRUMPFLOHNER: And then we can use the createNxworkspace. You don't even need to provide a folder, it'll automatically pick it up where we are now. What we can give it is usually an organization name or something. We could use, like, Learn With Jason, or whatever we'd like to do. This will be the NPM scope, basically.
JASON LENGSTORF: Something like this?
JURI STRUMPFLOHNER: You can directly add it, as the first name, and go with it.
JASON LENGSTORF: I'm going to make it shorter.
JURI STRUMPFLOHNER: I was going to say, this is the NPM scope, so we might have to type it a few times.
You'll see a walkthrough of what we want to set up. Is this technology agnostic? It's optimized for more the frontend space. Here, we see a couple of presets that it comes with. The first two are an empty preset. You roll everything on your own. In our case, we would probably want to go with the React because it always sets up Babel for us. If you know primarily going to be React, that's what we go with.
Then we can give it an application name.
JASON LENGSTORF: So, we'll go with "site," because that is easier to type.
JURI STRUMPFLOHNER: Yeah, that's perfect.
JASON LENGSTORF: Roll plain CSS.
JURI STRUMPFLOHNER: That's fine.
JASON LENGSTORF: We'll go plain CSS.
JURI STRUMPFLOHNER: Cool.
JASON LENGSTORF: Nx file.
JURI STRUMPFLOHNER: This is Nx Cloud. This is basically the distribution of the caching on top of Nx. Nx comes with computation caching. If you want to use across teams, you can add in the Nx cloud plugin. You don't have to register immediately. You don't even have to register at all and you can immediately benefit from it. It's basically free for most workspace. Meaning� the cloud uses all [audio cutting out] if it saves you half an hour in your build, because it pulls that out of the cache, you would add that to its count. A couple months ago, we opened up Nx Cloud, every workspace gets 500 hours per month for free. If you're not a superhuge enterprise, want to go with more private cloud, Docker setup, container, you're never going to hit that limit. That's what I put here, it's free. It's basically free for everyone.
The workspace gets set up and since we have chosen React, it will set us� the React package and a couple of those things that might be useful.
JASON LENGSTORF: Great...and, so this, that we're setting up...there we go. Nx Cloud has been enabled and I'm just going to open this up so we can take a peek of what happened here. So, here is our repo. And I can see, in here, we've got our scoped app. And then we've got apps and libs and� okay. So, I did a couple things that I didn't want to do. Let me� is the scope tied to the folder name? If I change the folder name, is that going to break it?
JURI STRUMPFLOHNER: Not necessarily. Like, you can change the folder name totally. It's wired up internally.
JASON LENGSTORF: I was just thinking of publishing the repo, like this, and having this Learn With Jason. If I drop this� a folder level up, which I can do by...opening this. Let's go in here. GitHub. Learn With Jason. React Monorepo Nx. And then I'm going to take this up...copy this. We're going to delete that folder. And then, rename this one...I'm going to� am I still in this folder? Yes. I'm going to "get init" so that� so, we'll try that one more time and that is open. Okay. So, why don't you see...I'm going to close this and reopen it because I want the� it's ignoring everything and I think that's a symptom of me opening everything.
We've got our apps and libs, as you mentioned. That's very cool. Let me make this bigger, so we can see what's going on. And, we've got� Nx Console Extension is recommended. Should I install that?
JURI STRUMPFLOHNER: Yeah, we'll need that later, for sure. Whenever you set up Nx Workspace, this is one developed by the core team, at Nx. So, basically help you, if you're not a CLI type of guy, it is used for exploring what is possible within the workspace.
JASON LENGSTORF: For sure. Okay. So, let's just then� let's start where I always start, which is by peeking at the package JSON. We've got a couple things for Babel and stuff and NRWL things related to Nx, types, Babel, lint plugins. Pretty standard app setup, from what I've seen.
JURI STRUMPFLOHNER: This basically varies depending on which setup you chose. The React Preset, it pulls up� the novel libraries. If you had chosen with [Indiscernible], you'd get Nx and that's more or less it you'd need to pull in whatever you need along the way.
JASON LENGSTORF: Got it. Got it. Got it. I see this Nx.JSON. So, this would be if I have a private package that I'm not going to actually publish, but I want to be able to install, it would be @lwj and the name of the package. Is that the correct way to think about it?
The Nx, in here, is a conflict file. Nx reads whenever it runs command. You could customize stuff in here. The whole� the LWJ, the setup is not here, it is happening at TS Config base. If you see those path variables down here, which right now, is MT. Whenever we start adding a library, it would use that NPM scope and create a pathmapping and point you to whichever lib folder you created.
JASON LENGSTORF: Okay. That makes sense. So, then we've got our folder full of libs, which is empty. And we've got our app, which is the site that we set up. If we look in here, this looks pretty familiar as a React app. I see TS config. We've got a Project JSON. Is this related to Nx?
JURI STRUMPFLOHNER: Yeah, exactly. This is basically wrapping the tasks that we can execute on a project, right. Nx actually allows you� or would allow you to have a package JSON here and NPM scripts. Once you want a complex setup, maybe a different CSS style set, like SaaS, you can customize that. The thing here is what you see is the main point is if the targets object, if you collapse that, we see the different targets, if you collapse the build, you see here, we have preconfig. And these are� you can imagine those as being your NPM scripts. You can use it to run the build�
JASON LENGSTORF: Is this here, is this the console tools that's adding this?
JURI STRUMPFLOHNER: Exactly. That's one of the features that automatically reads those files. You can run it directly this way.
JASON LENGSTORF: Nice.
JURI STRUMPFLOHNER: And the whole point�
JASON LENGSTORF: If I click this, does that start the�
JURI STRUMPFLOHNER: Yeah, that will start our service, our application. You can see the command you can run. You would run Nx Serve, and it kicks off the actual application.
JASON LENGSTORF: This is cool. Okay. I haven't used this code [Indiscernible] stuff. So, this is really cool. So, we now have run a single command and we have the ability to just do the Nx Run Site Serve and there was a shortcut if we run Nx Serve, it'll pick up the site?
JURI STRUMPFLOHNER: You can use Nx, the time of the target and the name of the application, which would be "site," so it would run that specific target. And the whole point about the target is you want to actually wrap what is going on under the hood, right. Because usually what we would have, we would have a [Indiscernible] and NPM and run it in that case. Nx has one of the design principles that we don't want to break people.
Last week, we released version 14, which we use on a half a year regular basis. There's a dedicated command which would bring you from a previous version of Nx, including things like dependencies to the next version. You need a certain level of abstraction around what you do. Because, like, you can extend the local config here, we know how difficult that is to maintain over time and to migrate that automatically. So that's basically one of the reasons why we have, like, those project files in there. It's [Indiscernible] consume to reason about your workspace and what is going on. And, yeah...
JASON LENGSTORF: Well, cool. All right. So we now have a running site and we've done very little to get that running. We just had to run that Create Nx Workspace. And, now I think we're in a place where we can, you know, we can start making some changes. So, if I go into my app, we've got the Nx Welcome. So, if I was going to do something here, I could drop this off and we could say, like...let's do H1 and if I� all right. So, now we're building� we're building some stuff. But, I am a serious� I'm serious about this site, I want it to be really scalable, maintainable, I want to build a package or library, that I can install, that will give me some reusable [audio cutting out] or some shared components. If I wanted to do that�
JURI STRUMPFLOHNER: Since we have Nx Console install, you can go to the documentation, which are pretty good. Socalled generators you have, which are there for basically creating new code in your Nx Workspace. So you don't have to do that on your own and figure it out on your own.
Basically, if you go further down, for instance, you see an API coming in. Workspace structure...guides how to create it. Then API. We see the React one, for instance, in the middle somewhere. There is generators being mentioned, library generator or [Indiscernible] generator. So, we could go from here, kind of copying the commands and then trying to understand how they work or just use a more visual approach and use Nx Console.
JASON LENGSTORF: Nx Console is built in here so I can go here...
JURI STRUMPFLOHNER: Exactly. You can go, at the very top, click "generate." And then, just, like, choose "library," in this case.
JASON LENGSTORF: Neat!
JURI STRUMPFLOHNER: What this does basically, it reads the React library package. It understands there's a socalled generator of metadata and renders it. You can start basically giving it a name in a more visual way. You don't need to know the command. You can go with the name [Indiscernible] exactly.
JASON LENGSTORF: Okay. And if I leave this, does this� will it just default or�
JURI STRUMPFLOHNER: Yeah, this is fine. Actually, in the terminal below what you will see is, as you make the changes, behind the scenes, Nx Console, it will do dry runs. So, it will actually run the command.
JASON LENGSTORF: Oh, cool! Okay.
JURI STRUMPFLOHNER: Exactly. Here's what would happened. It would create, in the libs folder, shared components folder, a couple components. As you change the settings, it'll do dry runs behind the scenes. It shows you, this is what you would get if you hit the "run "button at the top. We can go with the default one, that is probably gives us most what we want.
As we also discussed before, like, here, we also have the option for the publisher flag. I want a library here that I don't just want to consume locally, this would set you up with a build config for the library, itself, which otherwise you wouldn't get. You wouldn't get a package JSON because you don't need it for the normal default set up where you just consume it within the workspace.
JASON LENGSTORF: Right. So, in this case, it would be like an internal library. But if we wanted to make it� something will consume internally. We could theoretically publish it later, I could just check this box and now we'll get� import path.
JURI STRUMPFLOHNER: For publish of libraries, there's a box, basically, in that form, here, where you need to give it an actual import path. In our case, we have a simple library where we have it called "shared UI." However, NPM has the limitation where you cannot have subpath. You can have a shared folder and then, slash, some other folder.
And so exactly. So, in this case, you would not get a package JSON that allows you to publish it also, individually. And not just consume it from your workspace.
JASON LENGSTORF: Very cool. We could do routing. We could skip formatting, TypeScript, standalone config, TS Strict mode and the tag runner, we could have that on or not. I think we've now got a project config and if I hit "run," it goes?
JURI STRUMPFLOHNER: Yep.
JASON LENGSTORF: On, we go. Okay. So, then if I go to my libs, I've got shared components. There's all my stuff.
JURI STRUMPFLOHNER: Exactly.
JASON LENGSTORF: This is very cool.
JURI STRUMPFLOHNER: In this case, for instance, since you clicked the publish flag, it gets its own JSON, we can compile this and bump the version there. You can compile it to this folder from there, to some registry.
JASON LENGSTORF: Yeah. Okay. So what we could do here is we could have one that would be, like, our shared header because that's going to be a thing, call it the "page header" for simplicity and we would give it the props, the shared component props and maybe we get a� a title. Oh, no, let's use "children," actually. That seems like a good idea because we can pass in our nav that way. So, we can do an arel home with an HF here and we'll say, whatever...and we can drop the children...and you don't like this for reasons...children, props, but multiple children were provided?
JURI STRUMPFLOHNER: You need to probably use props.children, as well, because it comes over that one, I guess.
JASON LENGSTORF: Props.children. That seems like it's going to be happy. Good, good, good. So, now, we have a� a basic thing, here. And then I don't really need a default, so I can�
JURI STRUMPFLOHNER: No, we can just remove that one.
JASON LENGSTORF: I screwed that up. Get that out of here. Good. Good. And then if I go into my shared styles, I can do something like, um...page header and we'll have it go, you know, display, block, margin, 0, padding, to [Indiscernible] 5 here and a background color of� we'll go black, color white. We'll make it simple. And then what we also need to do, then, to make that work, is we'll do a pitch header and we'll do a color of inherit so that it doesn't look too janky.
It's module.css, so we go "styles." So we can just rename this to "container." So now it lines up with our container.
Okay. Now I'm running. If I come back to my app.tsx, and I instead do an import page header on� wow, it autocompleted, that's amazing. I drop in� let's do a fragment...then I'm going to say, "drop in my page header." And inside of it, I'm going to put in our nav. And our nav will be� we'll do, like, an A, HREF, home, let's add one for "about." Those are going to break, but they will work, I think� and are we still running? Like, if I go out and look� do I need to restart to get it to pick that up?
JURI STRUMPFLOHNER: Yeah, let's try. Otherwise we can try the actual path mappings. Should be fine.
JASON LENGSTORF: Okay. So let me go back out here� where was it?
JURI STRUMPFLOHNER: We actually used Nx Console. You can also go and use the rightclick, as well. If you go back to the visual explorer of the files, if you rightclick on the site project...you should see Nx Run. Let's try that.
JASON LENGSTORF: Whoooooa.
JURI STRUMPFLOHNER: And then you can click "serve." Now it should serve our application.
JASON LENGSTORF: There we go. So, lots of issues with the specifics of the styling there. This is pretty dang cool how well this just ran. Right. So, we have� as far as I'm concerned with my� my building, I'm using shared components and that's a separate package. But Nx just makes that work. Right. Like�
JURI STRUMPFLOHNER: If you go, for instance, now, into TS Config, you can see how it actually gets pulled in. You made it publishable. You don't have to make it publishable. It'll pull it in from there [audio cutting out] but at the same time now, if you go into that shared components, we should see there, a similar project JSON that we had before. The project JSON should now have a build target to find. You made this publishable, meaning that you might want to publish that to an internal registry or public registry so you want to build that library independent of any other consuming application. You have the rollup set up, you could hit the component build for that library, you get an output into this folder and do what you normally do to publish, so an NPM publish or whatever that is.
JASON LENGSTORF: Extremely cool. And, like, I think this is one of those things that, you know, like, Nx is a tool, it's got a lot of [Indiscernible] buttons and it does a lot so you got to take time to learn the tool. But just seeing how quickly this all comes together where, you know, I have a project, that site, that's going to be consuming that package. I need that package to be available internally, but not necessarily part of that codebase because a different team is going to handle or shared components and that team can do whatever they want as long as they honor this API of giving me a header that accepts props and children. I should update this to match, so that people understand what's going on there... So, we get our page header props and as long as that matches, you include these, then we're in pretty solid shape here. Like, we can do, like, site title and make that a string. And then, update this to be site title. [Indiscernible] props.sitetitle. There it is. If I go in here and I look, oh, look! It's broken because it's missing site title. Just the ability to communicate across teams, through code when you start looking at the strengths of tooling, like, TypeScript is incredible for this. It's such a good communication tool. And, something like Nx, where if you make that change in the repo, at, you know, let's say we� we're all working off of a feature branch. If you make a change and I pull down those changes, my code immediately tells me where I need to make changes. It's just powerful. Like, you can just feel how much work disappeared. I don't have to call anybody. I don't have to get on anybody's calendar. I can just send them this updated stuff and as soon as they pull the changes, the code communicates. You don't have to do that human layer of, hey, you need to review this thing or that thing because we just made a couple changes.
JURI STRUMPFLOHNER: Exactly. Now, technically, we don't even have a monorepo. We have an app. We have an app pull in some shared logic from a lib within the same codebase and that's also actually published a blog post, you can totally use this as your React CLI if you want, right. That's also what I recommend. You go with the preset with React, that will set up everything for you. If we didn't have to set up any TypeScript mapping. We didn't have that knowledge. We can build that up as we go, as we learn, on the way. We didn't have to think about building with [Indiscernible] our library was rollup. The code ID, recognizing the TypeScript types, we don't have to think about any of this.
As you go and become more proficient, you can totally just dive in and customize the hell out of it. You can write your own executers. You can write your own plugins, then you get more advanced. But to get started, it's super easy. If you create a single React application from the start. You're thinking, okay, now I'm going to create that huge monorepo. While you're thinking about that application and how to get going quickly with that, right?
JASON LENGSTORF: And you can really see, if we start kind of breaking down what each of these things does, it's pretty easy to start identifying, okay, so we've got that we can talk about. But we've got apps. We've got libs and each of these is just kind of a list of what we have and what we need. We haven't talked about the testing yet, but we can talk about that a little bit.
But, this feels� if you use descriptive names, obviously, then you have the ability to� to kind of look in here and say, okay, so, this is the site. And then this could be the dashboard. Another way I could see this working really well is as asks get complex, you start to see not just a web property be owned by a team, but actually the� the whole, like, each route is owned by a team so we have micro[Indiscernible]. What a great way to operate a micro frontend is through this.
JURI STRUMPFLOHNER: Yeah, you basically� that's also a recommendation which we often have. Your app is really thin. So, for each route, you would create a dedicated library, where you pull into that. So the logic and the team can focus on the app down there. And what you can do there, as well, we could actually try that out. If you go to the terminal, you can run Nx Graph or NPX. It will leverage that internal graph and you can click on "show our projects." What we see, now, is visualization of how it's structured.
You can create libs per route and see structurewise. Maybe later on, you have a route that consumes the shared components or the authentication library. There's something� Nx 14, we dropped extensive for that. You might want to split your application into this vertical micro frontends. You have your site project, which pulls in different routes which are then even compiled separately so you can even optimize or take optimizations out of them.
Yeah, this graph, specifically, as really powerful as you become bigger, as your workspace grows and you have more libs in that space.
JASON LENGSTORF: Okay. So, we have about� call it 30 minutes before we have to wrap this episode. Did you have any specific features you wanted to show off or can we make a run at building a micro frontend here?
JURI STRUMPFLOHNER: The micro frontend one, probably not yet. We're still optimizing some pieces of it. We have Nx [Indiscernible] at the end of the week. We have�
JASON LENGSTORF: Is that virtual?
JURI STRUMPFLOHNER: Yeah. Yeah. If you go to Nx Conf. At the end of the week, we have basically a virtual conference. We announce some of the features we are building into 14, which is microservices support, as well as tons of other features. It's a couple hours, so that's why we call it Nx Conf Lite. It's free, so definitely jump in, register and we can see you there.
JASON LENGSTORF: Yeah, yeah. And this is one of those things that, you know, if you're thinking about using tools like these, one of the best ways to think about if they work in your setup is to present in a space like this. We get a lot of monorepo experts in one room. Great way to validate if the way you're thinking about a project is going to get you the best benefits. So, yeah, definitely go and register for that, chat.
Okay. So, what� with the remaining time that we have, I feel like we can show off a few aspects of things. So, what do you feel like is the best use of the remaining time to keep building out here?
JURI STRUMPFLOHNER: Yeah, sure. Now that you have, for instance, a shared component library set up, what we could do is add Storybook to that. That is the common thing for what you see. We're building that within a monorepo so we can generate a Storybook set up for that. Yeah, let's have a look. Usually you need to install a plugin, first.
JASON LENGSTORF: I'm going to try "generate."
JURI STRUMPFLOHNER: Try "storybook." Then you need to give it a project name, which is sharedcomponents.
And so then you see already the [Indiscernible] happening below. It also sets up, like, Cypress for you. Storybook can load single components, in a browser, so you can easily have Cypress [audio cutting out] browser perspective.
JASON LENGSTORF: Okay.
JURI STRUMPFLOHNER: And it should also generate stories for us. It should generate a story for us, so we can actually go in and inspect that. So we can actually go ahead and just click the "run" button...
JASON LENGSTORF: Okay. So, here is our� let me expand this a little bit so we can see. So here's our shared components endtoend test and that's installing. And now�
JURI STRUMPFLOHNER: Storybook apps probably.
JASON LENGSTORF: If I�
JURI STRUMPFLOHNER: If you go into the libs shared components library down there, you should already see the Storybook setup. This is the config for storybook. In the source folder, we should see some stuff related to Storybook, in the sense of the story that has been generated.
JASON LENGSTORF: In the source code?
JURI STRUMPFLOHNER: In the source folder, exactly.
JASON LENGSTORF: Here's a spec.
JURI STRUMPFLOHNER: Let's inspect the folder. Like, the terminal output to see what it printed out there.
JASON LENGSTORF: Created...pictures, examples, good.
JURI STRUMPFLOHNER: Well, let's try it out, then, by generating the stories directly on to the folder. If you rightclick, for instance, on the libs there, the libs shared storybook, that is a shared storybook name.
JASON LENGSTORF: Sorry, you're going to have to say that� here?
JURI STRUMPFLOHNER: In the shared Storybook. If you rightclick on the lib folder down there� it should be the lib folder [no audio].
JASON LENGSTORF: And then "generate"? It looks like you're muted...
JURI STRUMPFLOHNER: That's why it didn't generate them, because you were missing another Storybook dependency. We generated the configuration. So if you go to the package Jason at the very root of the workspace, we should have a look whether we have the actual plugin. Nx is constructed as a plugin layer and so now Storybook should be here. That's interesting. It looks like the Stories generator is missing. If you go on the menu, on your Visual Studio Code, to the Nx Console, exactly. Go to "generate" again. Storybook� yeah, we can run the Storybook, that will totally work. We don't have any stories. We can totally go ahead and build our own.
Usually if you just type "stories," it should come with� oh, here it is. Probably didn't see it before. If you go to React� exactly. Hit the second entry here.
JASON LENGSTORF: Ahhah. I know what happened. I wrote out "story" with a "y." So that's just me getting ahead of myself. If I come back up here to the shared component� was it shared component or was it lib?
JURI STRUMPFLOHNER: It should work either way. The rightclick optimized out the path searching. So if we search Stories, we should have it preselected and this should generate a story for us. Let's see whether that works...doesn't look like it found something. So, either� huh, that's interesting. Could be a regression. Basically what it does is it used the shared component, which you already had, and generates a story for you so you don't have to. You could wrapup a story on your own, basically.
JASON LENGSTORF: Okay. This is one of those, like, if I� create stories basis for everything [Indiscernible] could I have broken it by not exporting a default?
JURI STRUMPFLOHNER: It could be. We could try that out. It could totally be. Because behind the scenes, generator tries to run through and it scaffolds out stories. Once you have a couple stories, you will use those and get going�
JASON LENGSTORF: There it is. It required a default. I broke it when I removed that default.
JURI STRUMPFLOHNER: It scaffolds out a preset story for you. You can see a side title and we can run Storybook directly. Rightclicking, that should work...
JASON LENGSTORF: Run.
JURI STRUMPFLOHNER: Yeah, you can click on the shared components one. The one you clicked on is for the [Indiscernible] basically. But this one, here�
JASON LENGSTORF: Oh, run Storybook.
JURI STRUMPFLOHNER: This has the Storybook set up and you should run it and it should run a Storybook for us.
JASON LENGSTORF: Okay. Off it goes.
JURI STRUMPFLOHNER: This is useful, especially to get going. If you have a bunch of components and you want to add Storybook, you don't have to think about adding it. You can use that setup to generate stories for you. Especially if you create such design libraries as you did, this is superhelpful. You can publish this as documentation. The team responds before design system, they can use this to test their components and code them individually and then you can just basically include it in the application, as we did before.
JASON LENGSTORF: Uhhuh. I mean, this is� yeah, this is� [Laughter]. I'm honestly� like, I didn't expect us to get this far, this fast. [Laughter]. Like, the fact that we were able to do all of this work, this quickly. So, I'm� I'm now just kind of looking at this, thinking, all right, we talked a little bit� okay, so, we built some of this, where we have Storybook. We've got our shared components. We've got our app. We generated this endtoend test. If we want to show that effected testing, when you make a change in one, it changes the others, we could do a quick test to make sure that whatever you enter is the title� the site title shows up in the component and figure out how to run that test real quick?
JURI STRUMPFLOHNER: Now we have two endtoend tests. We used Cypress to set that up. If we go up to that test, up there, to that shared endtoend test� in the apps folder, basically...
JASON LENGSTORF: Shared endtoend� here.
JURI STRUMPFLOHNER: Yeah. Exactly. If you open up here� oh, because it didn't pick up the story initially. So we have to do it, basically on our own. We have to create the Cypress integration test. We can pick it up from the site endtoend test further down. Here, we have set up a test, but that is for the application. We can copy/paste the integration folder. We can name it, [Indiscernible] component.
JASON LENGSTORF: So, let's� we call it "page header."
JURI STRUMPFLOHNER: Yeah," page header," that makes sense.
JASON LENGSTORF: Here, we would want to get page header from JSON shared components and then�
JURI STRUMPFLOHNER: Is this� yeah, we don't even have to import probably the component itself. In this case, here, what we would want to go here and do, like with Cypress, boot Storybook, load the component page in the browser and then see, does it render what I want it to render? Storybook is still open and running, if you go to that instance, in the actual path there, you can see how Storybook, for instance, loads those up. There is the [Indiscernible] and everything is encoded into the URL. You just boot Storybook within Cypress.
JASON LENGSTORF: That's what we need.
JURI STRUMPFLOHNER: Yeah.
JASON LENGSTORF: I'm going to need you to walk me through how to set it up in Storybook.
JURI STRUMPFLOHNER: [Audio cutting out] it should be iframe. Let's go back to the storyrunning instance, the Storybook instance you have in the browser. Exactly, this one. I think there should be� like, if you rightclick within that story, here, and you framesource, what does it show? Can we see the URL, maybe? It should start with "/iframe." Here, you see� yeah. Let's copy this. This is where we want to navigate. We don't really need the navigation bar Storybook. We just need the single page. The ID, here, would be the one that I have up there, which is Page Header, I guess? Yeah, basically we can delete...um...yeah, we can just use the page header, dash, dash, primary. Yeah, exactly. If we then paste in this below, just after ID, basically...
JASON LENGSTORF: In place of this?
JURI STRUMPFLOHNER: Yeah, exactly. Usually this gets generated, so I don't even know it by heart. You generate and then copy/paste. Now, we can actually�
JASON LENGSTORF: We don't need to tell it to start Storybook or anything?
JURI STRUMPFLOHNER: Nx knows these two are wired together. In the project JSON, the endtoend target would point to the serving of the Storybook so this is basically handled behind the scenes. We just� we need to go and use [Indiscernible] find some piece of logic that we want to capture.
We can use�".get."
JASON LENGSTORF: [Indiscernible] get.
JURI STRUMPFLOHNER: And then a CSS selector that would capture or�
JASON LENGSTORF: Show up the way we want it to, right, where it shows�
JURI STRUMPFLOHNER: Maybe let's add some [Indiscernible] I use a data attribute of the components, just for the purpose of testing.
JASON LENGSTORF: I think that's a good idea.
JURI STRUMPFLOHNER: You have datatested. I think everything [Indiscernible] test ID, but it wouldn't really matter.
JASON LENGSTORF: Okay. So, we can do a "get data test ID equals page header." I just do that, right?
JURI STRUMPFLOHNER: Yeah.
JASON LENGSTORF: Okay.
JURI STRUMPFLOHNER: And then something like, should be visible, or even just getting the element. Between [Indiscernible] or "should exist."
JASON LENGSTORF: Let's go with� oh, wow. Contain text...
JURI STRUMPFLOHNER: Oh, okay. Yeah.
JASON LENGSTORF: Because we're passing the Learn With Jason. So, theoreticallyspeaking, that's a decent test. It should show up true. [Laughter]. So let's give� [Laughter]. Let's give it a shot.
JURI STRUMPFLOHNER: To start with, stop the Storybook running right now. And then you can run basically NPX. Nx. And then basically, the endtoend, so, E2E. Let's go through project [Indiscernible] if we go here, project JSON, that's where we would go if you don't know how to boot it. In the endtoend, you can click. You have to now [Indiscernible] Cypress, the executer is the logic that runs behind the scenes, it's like your NPM. It knows it needs to run Storybook and then it runs the actual test.
JASON LENGSTORF: Dang!
JURI STRUMPFLOHNER: Let's see whether [Indiscernible] green run already on the first try. Otherwise, if you do it incrementally, pass the dashdash.
JASON LENGSTORF: Cypress is loading. Where's it going to open? That's the real question.
JURI STRUMPFLOHNER: By default, it runs it in headless mode. Now we basically see our test running.
JASON LENGSTORF: So, it's running...come on...one passing.
JURI STRUMPFLOHNER: You're so great, you already got a passing immediately on the first try.
JASON LENGSTORF: Unbelievable.
JURI STRUMPFLOHNER: If you do this incrementally, you can pass a watch flag, right. And it would open up the Cypress test running, and we'd see interactively how the test is run. This is how we'd do with CI so you don't need a liverunning browser there. This is behind the scenes, Storybook, and ran a test against that.
JASON LENGSTORF: That's great. That worked. That did what we wanted. All good. There we go. We did it. We now have a test that runs against our shared components and it tests� it tests what we want. It's doing what we want.
JURI STRUMPFLOHNER: Exactly. Exactly. We could try the whole effect, on this endtoend test specifically. We could commit everything. What the Nx commands do is they use the Git history to understand, okay, what are you comparing against? Usually you have a feature branch, a couple comments there and compare, okay, like, since the main branch, this is what changed, these are projects in those commits so that's basically what happens when we run those effect commands.
JASON LENGSTORF: Okay. So, I've got my basic set up here, where I've got my� like, if we look, the main branch is clear.
JURI STRUMPFLOHNER: Cool. So, now we could create some sort of feature branch. What would happen if you would create a new feature, would you create a new branch and push it out�
JASON LENGSTORF: New feature and we'll call it, like, "updated h header," so we can see it break, right. So then, when I'm in here, I would do� I would run my site because that's what I would be trying to run.
JURI STRUMPFLOHNER: Uhhuh.
JASON LENGSTORF: Okay. Then, I would decide that I want my site to be� the site is up some running and it's functional. The changes I want to make are actually in here. I'm going to set up the� let me set this up to look a little bit better. We're going to call this "display flex" and we'll justify content...space between...and then we'll do� that might be enough. Let's see how that looks. Good. Okay. Then I can do, like, a font family and we'll do one of these. That looks� that looks wonderful. And then these, we make them display block, we can� we can do [Indiscernible] decoration, none. That didn't do what we wanted.
Why don't we make this container "nav, display, flex." And we'll give it a gap of, like, two rem. There we go. That works. So, now we've got a slightlystyled header, but then I'm going to break it. I'm going to go into my shared component. Instead of setting the props title, I'm going to hardcode it and say "page header." Maybe I was testing and I wanted to make sure that that worked. Okay. So, I think I'm doing.
JURI STRUMPFLOHNER: So now we go ahead and commit this, right. So we can just go ahead and�
JASON LENGSTORF: Got one of those. [Indiscernible] so we can see what I'm doing.
JURI STRUMPFLOHNER: So the next step would be push it upright and have it run through CI. We can do that after [Indiscernible] feature we add a couple weeks ago. To show, it locally, we could run a variation of the graph using NPX Nx but Affected Colon Graph. It would show what got affected by our changes, right. So, now, for instance, if you show our projects, here, or affected projects, this is a subset of what changed. Do we� don't have anything else right now in the repo? It's the lowestlevel library so everything's affected [audio cutting out] we wouldn't see that, right, on that affected graph.
Since we changed the lowest level, it works up and shows everything is affected. We have two shared components. We didn't run a site before so let's see what happens. And so if we go back to our terminal, we could then run our endtoend test, but only the affected ones and so to do that, we can run...
JASON LENGSTORF: Nx Run.
JURI STRUMPFLOHNER: I don't think they are in there. We would need to go to the menu then, I guess. Like, if you go to the menu, they are not integrated here. Let's see. The common Nx commands, you see affected endtoend. And you click that one. Exactly. This will run in the terminal and components. For different things, for the build� for the build, you would need that also, or for running linting against affecting libraries.
So, this is behind the scenes, pushes those up and runs those affected tests.
JASON LENGSTORF: That is extremely cool. Um...okay. So, and what I love about this is just how� you know, how� how many things just worked that I would have had to think about and plan and instruments and put together and, you know, like, there's documentation for all this. This is� this is kind of what you were talking about earlier, when we were looking at� what were our docs here? Here. Tons and tons of documentation on how to make this stuff work. Just really, really cool, like, with the generators and then you've got the different structures here.
And I don't have to build that myself, which, oh, my goodness, does that change everything.
JURI STRUMPFLOHNER: Actually, the generators, I think, are a pretty� a cool part that allows you to learn much quicker, right. One of my favorite features that we just landed last week is one that people struggle a lot, especially in monorepos, which is setting up CI. Setting up CI would mean, okay, now I have to think about how do I paralyze those runs with affected and [Indiscernible] and affects tests and builds and properly leverage those commands correctly. So, we can actually try it out, if we have time, to do that. Because we just added, this week� or last week, actually, a generator that sets up CI for you.
JASON LENGSTORF: Oh, no. What failed? Oh, it was supposed to fail.
[Laughter]. And that� but� this is working as designed. I screwed up my component, therefore it fixed. So if I come back here and I undo this change and we do the same thing again...no, I need to do it one more time, from over here. Right. Now while that's� what was it that you wanted to� to set up? Sorry.
JURI STRUMPFLOHNER: After this, we can do it once this finishes. One thing people struggle with is setting up for CI for all that stuff. Setting up CI for monorepo is be tricky so we added the generator to automate that fully for you. We didn't fullyannounce that yet. There's a lot coming on Nx Conf Lite this week. It is very much how we didn't have to think about setting up TypeScript and all those things today, when using the workspace.
JASON LENGSTORF: Now, is that something you think we can set up in about two minutes?
JURI STRUMPFLOHNER: I think so. We can stop here. So if you go to the "generate again," the "generate" command and type" CI." Then you click "CI Workflow." So, now we can choose basically CI provider. Let's go for GitHub, for instance. And then basically we can just run. So what this does now, you now should have a�.GitHub folder in your workspace... if your web CI YAML file. And this is kind of the power setup. We have a couple lines here, we have all these affected commands. These run with Nx Cloud behind the scenes. You can run them in affected� in the sense of what changed since last time, but also paralyze it in a proper way, in the most optimal way. It can build first, like, the lowerlevel nodes in the project graph and add apps on time and paralyze.
JASON LENGSTORF: Really cool.
JURI STRUMPFLOHNER: I think this is going to be� since we landed this� a killer feature. There are only a couple lines that you would have a hard time setting up on your own. For people who are interesting, come to Nx Conf Lite this week because we'll show some more stuff exactly around this feature.
JASON LENGSTORF: Very, very cool. Make sure you go and sign up for NX Conf, let me drop the link one more time for y'all.
I think that's as much as we're going to have time to cover today. I feel like this is the very beginning of the rabbit hole of how much we could do if we started setting it up. But what I'm really excited about is how much of the plumbing of things evaporated. Because one of the big things that keeps me from writing endtoend tests in my personal projects is I get overwhelmed by the idea of writing this config. Knowing I can run a generate config and there it is pretty slick. That makes me pretty happy.
And so, you know, I could see myself maybe� maybe this� maybe this is it. This is the year, 2022's the year I become a testing person. [Laughter]. Get some tests on that personal site. Juri, for folks who want to go deeper and learn more, where should they head next?
JURI STRUMPFLOHNER: Yeah. So, the best way to follow along with [Indiscernible] publish. We have a lot in the pipeline we want to push out. Our Twitter account, Nx Dev Tools. Also, on our YouTube channel. That's basically where we currently push out a lot of content and you can also find us on dash.to. [Indiscernible] first channel, where we push out new video content and bitesized videos showing the workflow. This is the best place to kind of learn.
Yeah, or also ping me on Twitter. Always happy to help out.
JASON LENGSTORF: All right. I'll drop Juri's Twitter one more time. Make sure you go and give him a follow. Shoutout to the captioning, we've had Vanessa here doing the captioning from White Coat Captioning and that's made possible through Netlify, Nx and Backlight all kicking in to make this show more accessible, which I appreciate it very, very much.
Next week, I'm going to do a solo episode. We're going to learn about Netlify Edge functions. And the founder of Prisma is working on Content Layer, it looks cool. We're going to know check that out.
We're going� that is the wrong� figure out who� we screwed something up in our entry there. Looking into converting code into Figma. [Audio cutting out].
You can subscribe on Google Calendar or on Twitch or on YouTube, wherever you prefer to spend your time.
I think we're going to call this one a win. Any parting words for the chat?
JURI STRUMPFLOHNER: It was great fun being on the show. If you are interested in Nx, definitely give it a go. Ping me on Twitter if you need help getting started.
JASON LENGSTORF: All right, y'all. We're going to call this one a win. Chat, stay tuned. We will see you all next time.
Closed captioning and more are made possible by our sponsors: