ProNextJS
    Loading
    lesson

    Lego Components

    Jack HerringtonJack Herrington

    Next.js 14 introduced Lego Components. These components are complete and self-contained just like real Lego blocks.

    In this lesson, we'll convert the PokemonList component into a Lego component that can be shared between multiple applications in a Turborepo app.

    Creating a Shared Lego Component

    The first step is to move the PokemonList component into the packages/ui/src directory within the Turborepo. This will make it accessible to other applications.

    To ensure the PokemonList component is usable, we need to configure its output. Update the package.json file within the packages/ui directory to export the PokemonList component:

    // inside packages/ui/package.json
    
    {
      "name": "@repo/ui",
      "version": "0.0.0",
      "private": true,
      "exports": {
        "./button": "./src/button.tsx",
        "./PokemonList": "./src/PokemonList/index.ts",
        "./card": "./src/card.tsx",
        "./code": "./src/code.tsx"
      },
      ...
    

    We've specified that our Pokemon List component is accessible at @repo/ui/pokemon-list. Now, let's use this component in our main Next.js application.

    In our main application's page.tsx, we'll import and render the Pokemon List component:

    // apps/client-server-component/app/page.tsx
    
    import PokemonList from "@repo/ui/PokemonList";
    
    export default function Home() {
      return (
        <>
          <h1 className="text-4xl mb-5 border-b-2">Home Application</h1>
          <PokemonList />
        </>
      );
    }
    

    Creating a Second App and Reusing the Component

    To show the reusability of our Lego Component, let's create a second Next.js application within our Turborepo called second-app.

    At the terminal in the apps directory, copy everything fomr client-server-component to second-app:

    cp -r client-server-component second-app
    

    Then update the package.json' in the second-app directory to have a unique name:

    // inside apps/second-app/package.json
    
    {
      "name": "second-app",
      ...
    }
    

    Once the second application is set up, we can render the Pokemon List component with a "Second Application" header and know that it's working.

    However, there's an issue where the Tailwind CSS styles are not being applied to the Pokemon List component.

    Styling Lego Components

    We need to update the tailwind.config.js file in both applications to include the packages/ui directory in the list of directories that Tailwind CSS should scan for styles:

    // inside tailwind.config.ts in both apps:
    import type { Config } from "tailwindcss";
    
    const config: Config = {
      content: [
        "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
        "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
        "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
        "../../packages/ui/**/*.{js,ts,jsx,tsx,mdx}",
      ],
      ...
    

    With this configuration, Tailwind CSS will apply the necessary styles to the Pokemon List component which are picked up in both apps:

    both apps side by side

    Configuring Lego Components

    Lego Components can be thought of similarly to micro-frontends. Componets like the PokemonList are self-contained UI pieces that can talk to the server, manage their own state, and be dropped onto a page like Lego blocks.

    There are two primary methods to configure Lego components.

    Using props directly in the component is flexible, but can be repetitive depending on how you set things up.

    Using environment variables is ideal for settings that vary between development, staging, and production environments. In our server code, we can access environment variables using process.env.

    For this specific example, utilizing environment variables to define the Pokemon API URL is a more efficient and maintainable solution, especially when dealing with multiple environments.

    When you're building an App Router application, think about leveraging the Lego Component idea!

    Transcript

    When Next.js 14 was released, Verso did a huge rollout with lots of different presenters talking about different aspects of the App Writer, including Lee Robinson talking about Lego components. Now Lego components, like Lego, are meant to be used as building blocks in your applications. They are complete and self-contained within themselves. An example of this is what we just showed in the client server code, where you've got a Pokemon list that goes off, hits the Pokemon API, allows you to page through the different Pokemon, and it handles it all on its lonesome. As it turns out, surprise, that client-server repo was part of a Turbo repo.

    What we can do in this example is show how to take these Lego components when we can turn that Pokemon list component into a Lego component, and share them between applications, and have them just be completely self-contained, which is really awesome. So let's go and run our Turbo Repo. So I've installed it and now I'm going to run dev on it. And now we can see that we've got this Pokemon list and it's inside our application that then hosts that component. But let's say that we have another application and we want to consume it there.

    So first off, I'm going to identify this application. So go into page and we'll just simply put a header on top of it. All right, that looks good. Let's go take a look. Now we got our home application.

    So what we're going to do next is take this Pokemon list component, which is completely self-contained and move it into the UI repo and then reuse it in another application. So I'm just really going to drag and drop this into packages and into UI and into source. And we can see over in our app that we're now broken because Pokemon list is no longer there. So let's first go and fix the output of the UI so that we can then bring it in. So over in the package.json, we have the exports.

    So now let's go and export Pokemon list. All right, we specified that our Pokemon list maps to source Pokemon list index.ts. Let's hit save on that and let's go fix our Pokemon list in our page. So we'll go over and we'll say we want that from repo UI and Let's take a look. All right, it looks great.

    Let's actually make a small change so we can make sure that that dynamic linkage is there. So I go into the client component, I can say, Pokemon list from repo UI. All right, let's go and create a second application that uses this Pokemon list. So in the terminal over in our apps directory, I'm going to copy the existing application, client server component to second app. Now I've got two apps in there.

    I'm going to go and make a change to our second app to make sure that the package.json says second app. So they don't collide. And Then I'll go to our page.tsx and change this to say that this is our second application and I'll make it italic and bold. Now, let's run pmpm-dev from the top level, and that will run both of those applications. Then I'll bring up localhost 2001, and that's our second application.

    So good news, now our Lego application is running on both of our applications, but it's also unstyled on both of our applications. And we can see that the Tailwind styles are no longer being picked up. The reason that it worked originally was because we hadn't actually refreshed Tailwind and so we were just in happenstance picking up the old styles that we had before. So let's go and fix that. To do that we're gonna go over our Tailwind config And we'll add the packages UI directory to the list of directories that tail into the scan for styles.

    And now that works for our second app because that's where we changed it. Let's go change it in our first app. And there you go. Now it works in both. So we can look at them side by side.

    Isn't that awesome? So a few more thoughts on this Lego component concept. First, it is very similar to what you might call a micro front end concept. These Pokemon lists are self-contained pieces of UI that, because you can have server actions, can go and talk to the server on their own, manage their own state. And all you need to do is just, like Lego blocks, drop them onto the page.

    So whether you want to call them Lego components or micro front ends, the only aspect of micro front end architecture that you're not getting here is the runtime loading that you would get with something like module federation, where, say, home application would export the pokemon list component at runtime the second application would then consume it and any time the home application would deploy a new version of pokemon list it would automatically be consumed by the second application, even though the second application didn't actually have that code. It would get that code at runtime. Now unfortunately, at the time of this video, Module Federation is not compatible with the Next.js app router, and I don't expect that to be the case anytime soon. Another really important aspect of Lego components is how you configure them. You've got a couple of different options in that space.

    Let's take for example, the Pokemon list server. What if you want to have an option for where to get that URL? Well one thing you could do is have a property here, base URL, and you can simply just default that base URL to the URL that you wanted. Then you could use a text template string to bring in that URL. Another option if you didn't want to use properties would be to use environment variables.

    So in this case, you'd use process.env. You give it something along the lines of Pokemon API. Then you give it a default value if that didn't exist. Of course, in that case, you would need to use that property. Let's also fix that down here.

    All right, looks good. Now, in this case, I personally would go with the environment approach. I think having to specify the base URL in properties every time I started that component, if I'm going to have different APIs that I want to hit in local and stage and production is kind of a drag. To me, that's what environment variables are for. And the fact that this server code is running in Node on the server, and it has access to all those environment variables, makes using environment variables in this context really easy.

    So that's what I would go with in this particular example. Regardless of all this, the Next.js App Writer is the only place right now where you can use this Lego concept. And it is incredibly powerful. So as you're thinking about your application architecture, leverage these components that you can drag and drop between applications and then manage their own state. It is a very powerful feature that is only available in the Next.js App Writer.

    So make good use of it.