<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Dante De Ruwe</title>
    <description>The latest articles on Forem by Dante De Ruwe (@dantederuwe).</description>
    <link>https://forem.com/dantederuwe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F521522%2F81abc102-0d3b-463c-bfc3-d3e0a0c31260.jpg</url>
      <title>Forem: Dante De Ruwe</title>
      <link>https://forem.com/dantederuwe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dantederuwe"/>
    <language>en</language>
    <item>
      <title>How I Set Up Shared Agent Config for Our Team with APM</title>
      <dc:creator>Dante De Ruwe</dc:creator>
      <pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/dantederuwe/how-i-set-up-shared-agent-config-for-our-team-with-apm-3ncb</link>
      <guid>https://forem.com/dantederuwe/how-i-set-up-shared-agent-config-for-our-team-with-apm-3ncb</guid>
      <description>&lt;p&gt;I set up a shared Git repo for our team to maintain reusable AI agent packages: skills, instructions, coding standards, custom agents. We use &lt;a href="https://microsoft.github.io/apm" rel="noopener noreferrer"&gt;APM&lt;/a&gt; (Agent Package Manager) to distribute them to every project in one command. Think &lt;code&gt;npm&lt;/code&gt;, but for agent config.&lt;/p&gt;




&lt;h2&gt;
  
  
  Four problems I kept running into
&lt;/h2&gt;

&lt;p&gt;If you've been working with AI coding agents (Copilot, Claude, Cursor, Codex), you've run into a few of these.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration drift
&lt;/h3&gt;

&lt;p&gt;You spend time crafting instructions, coding standards, and skills for your project. It works well. Then you start a new project, and you copy-paste it all over. And again. A few months later, you've got a bunch of repos with their own subtly different versions of the same agent configuration. You improve the frontend guidelines in repo A, but repos B through N never get the update. Nobody notices until something breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool lock-in
&lt;/h3&gt;

&lt;p&gt;Each AI tool wants its config in a different place. Copilot reads from &lt;code&gt;.github/&lt;/code&gt;, &lt;code&gt;.copilot/&lt;/code&gt;, or &lt;code&gt;.agents/&lt;/code&gt; depending on where you put your config, Claude wants &lt;code&gt;.claude/&lt;/code&gt;, Cursor has &lt;code&gt;.cursor/&lt;/code&gt; and so on. If you want to support multiple tools, or keep your options open, you end up maintaining the same knowledge in multiple formats and keeping all of those in sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  No source of truth in the repo
&lt;/h3&gt;

&lt;p&gt;One way to avoid tool lock-in is to not commit those tool-specific folders at all. But then a new team member clones a repo and gets zero agent setup. They build their own from scratch, copy files from a colleague, or grab what they can from a shared location. Everyone ends up with a different setup, and nobody knows which one is "right" for that project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared and project-specific config blur together
&lt;/h3&gt;

&lt;p&gt;Skills that apply to every repo live next to skills that only make sense for one project. If you store agent config in your user directory, it follows you to every repo whether it belongs there or not. If you try to share a setup with a teammate, you're handing over a flat pile of files with no indication of what's generic and what's project-specific. Copying setups between repos drags along things that don't belong.&lt;/p&gt;

&lt;p&gt;I was dealing with all of these. It was getting out of hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the right tool
&lt;/h2&gt;

&lt;p&gt;I wanted a way to manage agent configuration as dependencies: declarative, versioned, and shareable across repos. Two tools stood out.&lt;/p&gt;

&lt;h3&gt;
  
  
  skills.sh
&lt;/h3&gt;

&lt;p&gt;If you've looked into sharing agent capabilities before, you may have come across &lt;a href="https://skills.sh" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt; (or its &lt;code&gt;npx skills add&lt;/code&gt; CLI), built by &lt;a href="https://github.com/vercel-labs/agent-skills" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;. It's a solid tool for installing community skills into your project with a single command.&lt;/p&gt;

&lt;p&gt;I started there, but ran into its limits. skills.sh focuses on skills&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, which are only one of the primitives you might want to share. I also needed to distribute instructions (coding standards, conventions), custom agent definitions, hooks, and prompt templates.&lt;/p&gt;

&lt;p&gt;skills.sh is a good fit if you want to grab a few community skills. I needed something that manages a full stack of agent configuration across teams and projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  APM: a package manager for agent configuration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://microsoft.github.io/apm" rel="noopener noreferrer"&gt;APM&lt;/a&gt;, or &lt;strong&gt;Agent Package Manager&lt;/strong&gt; , is a tool by Microsoft that does for AI agent setup what &lt;code&gt;npm&lt;/code&gt; does for JavaScript dependencies. You declare what agent packages you need in an &lt;code&gt;apm.yml&lt;/code&gt; file, run &lt;code&gt;apm install&lt;/code&gt;, and it pulls everything in, lockfile included.&lt;/p&gt;

&lt;p&gt;APM supports instructions, skills, hooks, agents, prompts, and MCP server configuration as first-class primitives&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. It also handles transitive dependencies, lockfiles, and deploying to multiple tools at once.&lt;/p&gt;

&lt;p&gt;APM is &lt;strong&gt;tool-agnostic&lt;/strong&gt;. It detects what tools your project uses and deploys the right files to the right places. Write your agent config once, deploy it to Copilot, Claude, Cursor, or any other supported tool.&lt;/p&gt;

&lt;p&gt;There's no central registry. Packages are Git repos, resolved over SSH or HTTPS. That means private repos work out of the box if you have Git access to them (APM reuses your existing Git credentials&lt;sup id="fnref3"&gt;3&lt;/sup&gt;). If you stop using APM, your deployed files still work in their native format. &lt;a href="https://news.ycombinator.com/item?id=47454448" rel="noopener noreferrer"&gt;The APM creator's HN post&lt;/a&gt; explains the reasoning: each tool vendor governs its own ecosystem, so a cross-tool dependency manager shouldn't introduce yet another walled garden&lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Dependencies can point to an entire repo, a subfolder inside a repo, or a single file. You can reference GitHub repos with shorthand (&lt;code&gt;owner/repo&lt;/code&gt;), use full HTTPS or SSH URLs for any Git host (GitLab, Bitbucket, Azure DevOps, self-hosted), or point to local paths for monorepo setups&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. This flexibility is what makes the shared repo pattern work: one Git repo, multiple installable packages as subfolders.&lt;/p&gt;

&lt;h2&gt;
  
  
  One shared repo, many consuming projects
&lt;/h2&gt;

&lt;p&gt;I took this further. Instead of maintaining agent config per project, I created a &lt;strong&gt;central Git repository&lt;/strong&gt; that acts as a catalog of reusable APM packages.&lt;/p&gt;

&lt;p&gt;APM's &lt;a href="https://microsoft.github.io/apm/guides/org-packages/" rel="noopener noreferrer"&gt;org packages guide&lt;/a&gt; suggests creating separate repos per concern: one for security baselines, one for coding standards. I tried a different approach: one repo with multiple packages as subfolders, connected through internal dependencies. Same composability, less overhead. One repo to manage, one place to review changes. Packages still stay independent and focused.&lt;/p&gt;

&lt;p&gt;The structure organizes packages by concern, and each project pulls in what it needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agents-repo/
├─ general/ → guidance for ALL projects
├─ development/
│ ├─ _shared/ → shared building blocks (internal dependency)
│ ├─ backend-dotnet/ → .NET-specific guidance
│ └─ frontend-angular/ → Angular-specific guidance

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each folder is its own installable package with its own &lt;code&gt;apm.yml&lt;/code&gt;. An Angular frontend project pulls in &lt;code&gt;general&lt;/code&gt; and &lt;code&gt;development/frontend-angular&lt;/code&gt;. A .NET API project grabs &lt;code&gt;general&lt;/code&gt; and &lt;code&gt;development/backend-dotnet&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this modularity matters
&lt;/h3&gt;

&lt;p&gt;The packages form a dependency graph.&lt;/p&gt;

&lt;p&gt;Both &lt;code&gt;backend-dotnet&lt;/code&gt; and &lt;code&gt;frontend-angular&lt;/code&gt; declare &lt;code&gt;_shared&lt;/code&gt; as a dependency in their own &lt;code&gt;apm.yml&lt;/code&gt;. &lt;code&gt;_shared&lt;/code&gt; contains things that apply to &lt;em&gt;all&lt;/em&gt; development work but not to non-development uses of the agent. Think instructions on how to write commit messages, a skill for addressing PR review comments, conventions around branching and code review. The stack-specific packages build on top of it. &lt;code&gt;backend-dotnet&lt;/code&gt; adds .NET conventions like solution structure, API design patterns, unit and integration testing practices, validation steps for builds and formatting. &lt;code&gt;frontend-angular&lt;/code&gt; adds things like component architecture guidelines, styling conventions, and its own validation flow for linting and builds.&lt;/p&gt;

&lt;p&gt;The underscore-prefixed &lt;code&gt;_shared&lt;/code&gt; folder is an internal dependency by convention. Other packages in the repo depend on it, but consuming projects don't reference it. They get it transitively.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;general&lt;/code&gt; package sits outside the &lt;code&gt;development/&lt;/code&gt; tree. It holds things that apply to &lt;em&gt;any&lt;/em&gt; project, no matter the tech stack: community skills like &lt;code&gt;skill-creator&lt;/code&gt;&lt;sup id="fnref5"&gt;5&lt;/sup&gt; and &lt;code&gt;grill-me&lt;/code&gt;&lt;sup id="fnref6"&gt;6&lt;/sup&gt;, and connections to tools the whole organization uses. Non-developers like functional analysts can pull in &lt;code&gt;general&lt;/code&gt; on its own, without any of the development packages.&lt;/p&gt;

&lt;p&gt;For an Angular project, the dependency graph could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;consuming project's apm.yml
├─ general (non-technical guidance, community skills)
└─ development/frontend-angular (or backend-dotnet)
   └─ development/_shared (transitive: shared dev tooling and skills)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A consuming project needs two lines in its &lt;code&gt;apm.yml&lt;/code&gt;. APM resolves the rest, and &lt;code&gt;_shared&lt;/code&gt; comes along because &lt;code&gt;frontend-angular&lt;/code&gt; depends on it.&lt;/p&gt;

&lt;p&gt;I update the PR review skill in &lt;code&gt;_shared&lt;/code&gt;, and every frontend and backend project picks up the change. I tighten the Angular coding standards, and only Angular projects are affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a project consumes shared packages
&lt;/h2&gt;

&lt;p&gt;In any project repo, you reference the shared packages in your &lt;code&gt;apm.yml&lt;/code&gt;. In this example, the shared config lives in a repo called &lt;code&gt;agents&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;git&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-git-host.com/your-org/agents&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development/frontend-angular&lt;/span&gt;
      &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;git&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-git-host.com/your-org/agents&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;general&lt;/span&gt;
      &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;apm install

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;APM resolves all dependencies, including transitive ones, and deploys the instructions, skills, and hooks into the right tool-specific folders (&lt;code&gt;.github/&lt;/code&gt;, &lt;code&gt;.claude/&lt;/code&gt;, etc.). You don't need to commit these generated files to source control, because &lt;code&gt;apm install&lt;/code&gt; can regenerate them at any time.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;--target &amp;lt;tool&amp;gt;&lt;/code&gt; (e.g. &lt;code&gt;--target claude&lt;/code&gt;) to deploy for a specific tool only. Add &lt;code&gt;--update&lt;/code&gt; to pull the latest versions of all dependencies before installing. You can combine both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;apm install --target claude --update

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pulling in community-built skills
&lt;/h3&gt;

&lt;p&gt;APM also supports pulling in packages from public GitHub repos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;anthropics/skills/skills/skill-creator&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mattpocock/skills/productivity/grill-me&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The community around shared skills is growing. You can mix community-built skills with your own organization's standards. Found a good skill on GitHub? Add it as a dependency!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: these two specific skills are fantastic. Anthropic's skill-creator&lt;sup id="fnref5"&gt;5&lt;/sup&gt; helps you build, test, and iterate on new skills with evals baked in. Matt Pocock's grill-me&lt;sup id="fnref6"&gt;6&lt;/sup&gt; interviews you about a plan or design until the gaps surface. I added both to my default setup and I can't imagine working without them now.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Local overrides with &lt;code&gt;.apm/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Some projects have quirks that don't belong in a shared package. For these, you can place project-specific instructions, skills, or hooks in a local &lt;code&gt;.apm/&lt;/code&gt; folder right inside your project repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├─ apm.yml
├─ apm.lock.yaml
└─ .apm/
   ├─ instructions/
   │ └─ local-conventions.instructions.md
   └─ skills/
       └─ my-project-skill/
           └─ SKILL.md

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run &lt;code&gt;apm install&lt;/code&gt;, APM picks up this local content and deploys it alongside your shared dependencies. No extra config in &lt;code&gt;apm.yml&lt;/code&gt; needed.&lt;/p&gt;

&lt;p&gt;I'm a bit proud of this one, because this feature &lt;a href="https://github.com/microsoft/apm/issues/626" rel="noopener noreferrer"&gt;started as a feature request I filed&lt;/a&gt; on the APM repo. Before v0.8.12, the &lt;code&gt;.apm/&lt;/code&gt; folder in your own project wasn't treated the same way as content from installed packages. You had to use workarounds like &lt;code&gt;apm install ./.apm/skills/skill-name&lt;/code&gt; to get local skills deployed. That felt inconsistent. The &lt;code&gt;.apm/&lt;/code&gt; folder is the natural place for project-local APM content, so it should work out of the box.&lt;/p&gt;

&lt;p&gt;I filed the issue, another user independently confirmed the gap, and the APM team shipped the fix within two days. That kind of responsiveness from an open-source project is refreshing, and it shows APM is still in active development with a team that listens.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get from this setup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DRY, for real.&lt;/strong&gt; Write your coding standards once. Share them across projects. When you improve a skill or update an instruction, consuming projects get the update on their next &lt;code&gt;apm install --update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool independence.&lt;/strong&gt; I don't want to commit to a single AI tool while this space evolves this fast. APM lets the same package of instructions and skills target Copilot, Claude, Cursor, or any other supported tool. If you switch tools next month, your agent knowledge comes with you.&lt;/p&gt;

&lt;p&gt;Different team members can even prefer different tools. Each person runs &lt;code&gt;apm install&lt;/code&gt;, and they get the config deployed for &lt;em&gt;their&lt;/em&gt; tool of choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easy updates.&lt;/strong&gt; Updating is a one-liner: &lt;code&gt;apm install --update&lt;/code&gt;. You can also pin to specific branches, tags, or commits for stability. Pin to &lt;code&gt;v1.2&lt;/code&gt; for a production project, or follow &lt;code&gt;main&lt;/code&gt; if you want the latest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smooth team sharing.&lt;/strong&gt; A new team member runs &lt;code&gt;apm install&lt;/code&gt; and has the same agent setup as the rest of the team. The config lives in a repo, versioned and reviewable in pull requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composability.&lt;/strong&gt; A consuming project picks the packages it needs. No Angular project inherits .NET conventions. No .NET project pulls in frontend linting instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supply-chain security.&lt;/strong&gt; Agent instructions have direct access to your codebase and terminal. That makes them a vector worth scanning. APM ships with &lt;code&gt;apm audit&lt;/code&gt;, which checks installed packages for hidden Unicode characters, bidi marks, and prompt-injection payloads. It outputs SARIF, so you can plug it into your existing code scanning pipeline&lt;sup id="fnref7"&gt;7&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD integration.&lt;/strong&gt; The lockfile (&lt;code&gt;apm.lock.yaml&lt;/code&gt;) pins every dependency to an exact commit SHA, so &lt;code&gt;apm install&lt;/code&gt; in a CI pipeline produces the same agent setup as on your local machine. There's also an official &lt;a href="https://github.com/microsoft/apm-action" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; if you want to automate installs and audits on every PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline support.&lt;/strong&gt; &lt;code&gt;apm pack&lt;/code&gt; bundles your resolved dependencies into a portable archive. &lt;code&gt;apm unpack&lt;/code&gt; restores them on a machine without Git access. Useful if you work in environments with restricted internet connectivity, or if you want to ship a self-contained agent setup to someone who can't reach your Git host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local overrides.&lt;/strong&gt; Projects with specific quirks can add instructions, skills, or hooks in a local &lt;code&gt;.apm/&lt;/code&gt; folder without touching the shared packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advice if you're doing this too
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keep packages narrow
&lt;/h3&gt;

&lt;p&gt;It's tempting to create one mega-package with all your standards, skills, and hooks bundled together. Resist that. Smaller, composable packages are easier to maintain. They let consuming projects pick up frontend guidance without also inheriting backend conventions they don't need. If two packages share common ground, extract that into a &lt;code&gt;_shared&lt;/code&gt; dependency rather than duplicating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only share what's reusable
&lt;/h3&gt;

&lt;p&gt;If guidance applies to one project, keep it local in that project's &lt;code&gt;.apm/&lt;/code&gt; folder. The shared repo is for patterns that apply across projects and teams. Putting single-project quirks in a shared package adds noise for consumers who don't need them, and creates maintenance burden when that project's needs change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat it like code
&lt;/h3&gt;

&lt;p&gt;Use pull requests, reviews, and meaningful commit messages. Your agent configuration shapes how AI tools behave across your entire organization. A bad instruction can propagate to dozens of repos on the next update. Give changes the same review rigor you'd give production code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consider not committing generated files
&lt;/h3&gt;

&lt;p&gt;APM's own docs suggest committing the tool-specific folders (&lt;code&gt;.github/&lt;/code&gt;, &lt;code&gt;.claude/&lt;/code&gt;, &lt;code&gt;.cursor/&lt;/code&gt;, etc.)&lt;sup id="fnref8"&gt;8&lt;/sup&gt;, and there are valid reasons to do so: cloud agents may need config present in the repo, and committed files give contributors instant context before they run &lt;code&gt;apm install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I chose not to, for now. My reasoning: these files can be regenerated from &lt;code&gt;apm install&lt;/code&gt; at any time, committing them repeats content that already lives in the shared packages, and I don't want the repo to prescribe which AI tool developers should use. If the generated config lives in &lt;code&gt;.gitignore&lt;/code&gt;, each developer runs &lt;code&gt;apm install&lt;/code&gt; and gets the output for their tool.&lt;/p&gt;

&lt;p&gt;I'm not 100% sure this is the right call long-term, but it follows a principle I like: if code is generated from source, don't commit the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;If you're managing AI agents across multiple projects and you're still copy-pasting instructions between repos, this setup is worth an afternoon of your time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install APM: &lt;code&gt;irm https://aka.ms/apm-windows | iex&lt;/code&gt; (Windows) or &lt;code&gt;curl -sSL https://aka.ms/apm-unix | sh&lt;/code&gt; (Mac/Linux)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;apm init&lt;/code&gt; in a project&lt;/li&gt;
&lt;li&gt;Add a dependency and run &lt;code&gt;apm install&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://microsoft.github.io/apm" rel="noopener noreferrer"&gt;APM docs&lt;/a&gt; walk through each step. The &lt;a href="https://microsoft.github.io/apm/guides/org-packages/" rel="noopener noreferrer"&gt;org packages guide&lt;/a&gt; covers the shared repo pattern in detail.&lt;/p&gt;

&lt;p&gt;If you do set this up, I'd be curious to hear how it works for your team.&lt;/p&gt;







&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://skills.sh" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt; focuses on installing skills (folders with a &lt;code&gt;SKILL.md&lt;/code&gt;) from GitHub repos. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;APM supports instructions, prompts, agents, skills, hooks, chat modes, and MCP server configuration as first-class primitives. Dependencies can be entire repos, subfolders, or single files from any Git host. See the &lt;a href="https://microsoft.github.io/apm/guides/dependencies/" rel="noopener noreferrer"&gt;APM dependencies guide&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;APM resolves authentication through environment variables, &lt;code&gt;gh auth login&lt;/code&gt;, or your system's Git credential helper. Private repos on GitHub, GitLab, Bitbucket, and Azure DevOps all work. See the &lt;a href="https://microsoft.github.io/apm/getting-started/authentication/" rel="noopener noreferrer"&gt;APM authentication guide&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;From the &lt;a href="https://news.ycombinator.com/item?id=47454448" rel="noopener noreferrer"&gt;APM creator's Show HN post&lt;/a&gt;: "Why this won't come from plugin vendors: each tool governs its own ecosystem. [...] Nobody governs across tools, resolves cross-plugin dependencies, or gives consumers a lock file for what they actually installed." ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://github.com/anthropics/skills/tree/main/skills/skill-creator" rel="noopener noreferrer"&gt;skill-creator&lt;/a&gt; by Anthropic. Helps you create, test, and optimize skills with built-in evals. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;&lt;a href="https://github.com/mattpocock/skills/tree/main/skills/productivity/grill-me" rel="noopener noreferrer"&gt;grill-me&lt;/a&gt; by Matt Pocock. Stress-tests your plans and designs through relentless questioning. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;Marcel Lupo wrote a good &lt;a href="https://dev.to/pwd9000/agent-package-manager-apm-a-devops-guide-to-reproducible-ai-agents-4c25"&gt;DevOps-focused walkthrough of APM&lt;/a&gt; that covers the security and CI angles in more detail. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;See the "What to commit" section in the &lt;a href="https://microsoft.github.io/apm/getting-started/quick-start/" rel="noopener noreferrer"&gt;APM quick start guide&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How to create a local gitignore file</title>
      <dc:creator>Dante De Ruwe</dc:creator>
      <pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/dantederuwe/how-to-create-a-local-gitignore-file-5hl2</link>
      <guid>https://forem.com/dantederuwe/how-to-create-a-local-gitignore-file-5hl2</guid>
      <description>&lt;p&gt;Sometimes, you want to experiment and tinker with stuff locally without git tracking your files. Think about experimenting with AI agents, local scripts, or even leaving yourself todo notes in every repo.&lt;/p&gt;

&lt;p&gt;In stead of carefully avoiding to stage and commit these files, we can let git ignore them without changing the repo's &lt;code&gt;.gitignore&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional gitignore files
&lt;/h2&gt;

&lt;p&gt;Did you know you can create additional gitignore files? These can extend your main gitignore file.&lt;/p&gt;

&lt;p&gt;You can let git know it should treat an extra gitignore file with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--local&lt;/span&gt; core.excludesfile &lt;span class="s2"&gt;"&amp;lt;my-gitignore-path&amp;gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, you can add your experimental files or folders that you're not ready for the world to see, away from the allseeing eye of git 👀&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping your gitignore file local
&lt;/h2&gt;

&lt;p&gt;We don't want this gitignore to be added to the repo! So we'll have to set it up to be local.&lt;/p&gt;

&lt;p&gt;We're going to do this as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a local gitignore file (call it e.g. &lt;code&gt;.local.gitignore&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add it to the git config as shown above:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a reference to the local gitignore file in the local gitignore file itself&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;🚀 Terminal oneliners for quick setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run this in the root folder of your git repository.&lt;/p&gt;

&lt;p&gt;Bash (Linux or Git Bash):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".local.gitignore"&lt;/span&gt; &amp;amp;gt&lt;span class="p"&gt;;&lt;/span&gt; .local.gitignore &amp;amp;amp&lt;span class="p"&gt;;&lt;/span&gt;&amp;amp;amp&lt;span class="p"&gt;;&lt;/span&gt; git config &lt;span class="nt"&gt;--local&lt;/span&gt; core.excludesfile &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/.local.gitignore"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Powershell (Windows):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gitignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".local.gitignore"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;core.excludesfile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;\.local.gitignore"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;p&gt;The contents of my local gitignore are mostly as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.&lt;span class="n"&gt;local&lt;/span&gt;.*
.&lt;span class="n"&gt;local&lt;/span&gt;/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ignores all files starting with &lt;code&gt;.local.&lt;/code&gt; (such as &lt;code&gt;.local.gitignore&lt;/code&gt; itself!), and also ignores a &lt;code&gt;.local&lt;/code&gt; folder where you can keep your super secret files with whatever names you wish.&lt;/p&gt;

</description>
      <category>git</category>
      <category>productivity</category>
      <category>tooling</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Switching up your Spotify experience with microfrontends and Blazor</title>
      <dc:creator>Dante De Ruwe</dc:creator>
      <pubDate>Fri, 07 May 2021 08:03:26 +0000</pubDate>
      <link>https://forem.com/dantederuwe/switching-up-your-spotify-experience-with-microfrontends-and-blazor-4k72</link>
      <guid>https://forem.com/dantederuwe/switching-up-your-spotify-experience-with-microfrontends-and-blazor-4k72</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46"&gt;&lt;strong&gt;my previous article&lt;/strong&gt;&lt;/a&gt;, I talked about creating a Netflix clone using &lt;a href="https://piral.io" rel="noopener noreferrer"&gt;Piral&lt;/a&gt;: an open-source framework for creating modular applications. I highly recommend giving that article a quick read first, if you are not yet familiar with microfrontends and/or Piral. &lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46" class="crayons-story__hidden-navigation-link"&gt;My experiences creating a Netflix clone using microfrontends&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/dantederuwe" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F521522%2F81abc102-0d3b-463c-bfc3-d3e0a0c31260.jpg" alt="dantederuwe profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/dantederuwe" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Dante De Ruwe
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Dante De Ruwe
                
              
              &lt;div id="story-author-preview-content-638460" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/dantederuwe" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F521522%2F81abc102-0d3b-463c-bfc3-d3e0a0c31260.jpg" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Dante De Ruwe&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 18 '21&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46" id="article-link-638460"&gt;
          My experiences creating a Netflix clone using microfrontends
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/piral"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;piral&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/microfrontends"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;microfrontends&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;237&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              8&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            23 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Since then, I've been making contributions on GitHub and working closely together with the maintainers of Piral, to provide a helping hand to improve their framework in any way I could. &lt;br&gt;
The main bulk of the improvements was made in their converter for &lt;strong&gt;Blazor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will share my experiences creating a microfrontend web app with Blazor and Piral. I'll also give a little &lt;em&gt;behind-the-scenes&lt;/em&gt; look at how this was made possible, highlight some of the quirks of using Blazor in a microfrontend solution, and explain how the combination of Piral and Blazor has improved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://spiralfy.party" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fwebsite%3Fcolor%3D1ed45f%26style%3Dflat-square%26up_message%3Dspiralfy.party%26url%3Dhttps%253A%252F%252Fspiralfy.party" width="92" height="20"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
Spiralfy: a modular web application

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;The Spiralfy appshell&lt;/li&gt;
&lt;li&gt;The player: a simple pilet&lt;/li&gt;
&lt;li&gt;The controls: a Blazor pilet!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Dev story: making Blazor work with microfrontends

&lt;ul&gt;
&lt;li&gt;Looking back&lt;/li&gt;
&lt;li&gt;What needed improvement, and why?&lt;/li&gt;
&lt;li&gt;What has changed and improved?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Final thoughts&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Spiralfy: a modular web application
&lt;/h1&gt;


&lt;center&gt;
&lt;a href="https://spiralfy.party" rel="noopener noreferrer"&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FGM0W1xr.gif" width="600" height="350"&gt;
&lt;/a&gt;
&lt;/center&gt;




&lt;p&gt;First of all, let's discuss the demo application that I created to showcase the use of Blazor with Piral: Spiralfy. A clever – or some would say &lt;em&gt;cheesy&lt;/em&gt; – play on words between Spotify and Piral, of course. But what does it do?&lt;/p&gt;

&lt;p&gt;Log in with your Spotify premium account, and access a way to switch up your Spotify experience!&lt;/p&gt;
&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;For a long time now, I wanted a way to shuffle play my playlists. I'm not talking about shuffling the songs within one playlist, that's something you can obviously already do. The feature I wanted could be described as "swiping through playlists": Spiralfy picks one playlist at random, shuffle playing its songs, and whenever you feel like you want a different &lt;em&gt;vibe&lt;/em&gt;, you let Spiralfy pick a new playlist to listen to.&lt;/p&gt;

&lt;p&gt;(I got inspired in part by &lt;a href="https://lofi.cafe" rel="noopener noreferrer"&gt;lofi.cafe&lt;/a&gt;, where you can switch through curated lofi playlists like they were radio stations. But I wanted the user to be able to use their own Spotify playlists instead.)&lt;/p&gt;
&lt;h3&gt;
  
  
  The code
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Talk is cheap. Show me the code" ~ Linus Thorvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Spiralfy is a modular distributed web application, also known as a microfrontend application. In my previous microfrontend project I decided to make separate GitHub repositories for each and every module; to really demonstrate that these are highly decoupled and autonomous. This time, I chose to bundle all parts into one repository (a &lt;em&gt;monorepo&lt;/em&gt;), just because it would be easier to discover and browse all the code at once. You can find the code on &lt;a href="https://git.io/spiralfy" rel="noopener noreferrer"&gt;git.io/spiralfy&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/spiralfy" rel="noopener noreferrer"&gt;
        spiralfy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A different way of using Spotify! Built using a microfrontend approach with Piral, Blazor and React. Read more on https://bit.ly/spiralfy-article
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/19cebea47ff21ac81b88e51ab08cb8eb3f3e30a4c8743e357ed7d683ee78ca54/68747470733a2f2f692e696d6775722e636f6d2f6c6647574450712e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/19cebea47ff21ac81b88e51ab08cb8eb3f3e30a4c8743e357ed7d683ee78ca54/68747470733a2f2f692e696d6775722e636f6d2f6c6647574450712e706e67" height="80px"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;switching up your Spotify experience with microfrontends and Blazor&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://app.netlify.com/sites/spiralfy/deploys" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d0f37c7c433ba0dcbad1b4a1213163f7cc37992b4aedd91dcacb0b10698bf4ac/68747470733a2f2f6170692e6e65746c6966792e636f6d2f6170692f76312f6261646765732f32626466363065662d323364342d346665342d393836382d3338313938643261653538322f6465706c6f792d737461747573" alt="Netlify Status"&gt;&lt;/a&gt;
&lt;a href="https://spiralfy.party" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2e21b36e3e0b0847fe48a9e775a48ebb834603c073ade3a4b1879d1832fb6af2/68747470733a2f2f696d672e736869656c64732e696f2f776562736974653f636f6c6f723d316564343566267374796c653d666c61742d7371756172652675705f6d6573736167653d73706972616c66792e70617274792675726c3d687474707325334125324625324673706972616c66792e7061727479" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;span&gt;Made with&lt;/span&gt;
&lt;span&gt;
  &lt;a href="https://reactjs.org" rel="nofollow noopener noreferrer"&gt;
    &lt;img id="user-content-react-logo" src="https://camo.githubusercontent.com/437589a3aa7f2673c4c7e8f9a45663ba10704f04b017e50861682fcbafb9efc8/68747470733a2f2f692e696d6775722e636f6d2f674e7877776e312e706e67" height="10"&gt;
    React
  &lt;/a&gt;
&lt;/span&gt;
&lt;span&gt;,&lt;/span&gt;
&lt;span&gt;
  &lt;a href="https://blazor.net" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/f0895030940f15d1d339eecd5216ffef6f640148eb3a656aacef77090843b521/68747470733a2f2f646576626c6f67732e6d6963726f736f66742e636f6d2f6173706e65742f77702d636f6e74656e742f75706c6f6164732f73697465732f31362f323031392f30342f4272616e64426c617a6f725f6e6f68616c6f5f31303030782e706e67" height="10"&gt;
    Blazor
  &lt;/a&gt;
&lt;/span&gt;
&lt;span&gt;and&lt;/span&gt;
&lt;span&gt;
  &lt;a href="https://piral.io" rel="nofollow noopener noreferrer"&gt;
    &lt;img id="user-content-piral-logo" src="https://camo.githubusercontent.com/893119eea72cada8be0ddb6dffc7f941a301b0ad4306a630b53f74e53623d025/68747470733a2f2f706972616c2e696f2f6c6f676f2d73696d706c652e66383636373038342e706e67" height="10"&gt;
    Piral
  &lt;/a&gt;
&lt;/span&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://spiralfy.party" rel="nofollow noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://camo.githubusercontent.com/2f8f35852c449c3de199abf6c158c38e1f9fe797b73592fc1e9915b403903c14/68747470733a2f2f692e696d6775722e636f6d2f474d30573178722e676966"&gt;&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Article&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://dev.to/dantederuwe/switching-up-your-spotify-experience-with-microfrontends-and-blazor-4k72" rel="nofollow"&gt;&lt;br&gt;
&lt;img src="https://camo.githubusercontent.com/8a4ef7f4a416e6903ce30bd55741551bafc6d512ab2fe3fa6b2aa414ab6e53fc/68747470733a2f2f692e696d6775722e636f6d2f4a715734456e552e6a7067"&gt;&lt;br&gt;
In this DEVCommunity article, I share my experiences creating a microfrontend web app with Blazor and Piral. I also give a little behind-the-scenes look at how this was made possible&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;First of all, let's discuss the demo application that I created to showcase the use of Blazor with Piral: Spiralfy. A clever – or some would say &lt;em&gt;cheesy&lt;/em&gt; – play on words between Spotify and Piral, of course. But what does it do?&lt;/p&gt;

&lt;p&gt;Log in with your Spotify premium account, and access a way to switch up your Spotify experience!&lt;/p&gt;

&lt;p&gt;For a long time now, I wanted a way to shuffle play my playlists. I'm not talking about shuffling the songs within one playlist, that's something you can obviously already do. The feature I wanted could be described as "swiping through playlists"…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/DanteDeRuwe/spiralfy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;For now, Spiralfy exists in 3 parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Piral instance: &lt;code&gt;spiralfy-appshell&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;player&lt;/code&gt; pilet&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;controls&lt;/code&gt; pilet&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What are these words?&lt;/em&gt;&lt;br&gt;
In case you did not read my previous article: a quick summary here. In the Piral framework, pilets are the individual feature modules, also known as microfrontends. Pilets are usually published to a feed service. The Piral instance (aka app shell) will pull all registered pilets from the feed service, and put them where they need to go as defined by the pilets themselves.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Spiralfy appshell
&lt;/h2&gt;

&lt;p&gt;For the Spiralfy app shell, I decided to go with &lt;code&gt;piral-core&lt;/code&gt; instead of the full &lt;code&gt;piral&lt;/code&gt; framework.&lt;/p&gt;

&lt;p&gt;I actually started with the full version of Piral, but after realizing that I will not be using any dashboards, translation, notifications... and other fancy features (bundled in a collection called &lt;code&gt;piral-ext&lt;/code&gt;) I migrated to &lt;code&gt;piral-core&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.piral.io/guidelines/tutorials/22-core-and-base" rel="noopener noreferrer"&gt;Piral docs page on &lt;code&gt;piral-core&lt;/code&gt;&lt;/a&gt; actually describes this scenario pretty well: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quite often the scenario is that somebody starts with &lt;code&gt;piral&lt;/code&gt; but then realized that one or the other plugin should not be included. (...) In any of these cases a migration from &lt;code&gt;piral&lt;/code&gt; to &lt;code&gt;piral-core&lt;/code&gt; makes sense.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There were only 2 plugins that I actually wanted: &lt;a href="https://npmjs.com/piral-menu" rel="noopener noreferrer"&gt;&lt;code&gt;piral-menu&lt;/code&gt;&lt;/a&gt;, in case I would want to add items to the navigation menu in an easy way; and &lt;a href="https://npmjs.com/piral-blazor" rel="noopener noreferrer"&gt;&lt;code&gt;piral-blazor&lt;/code&gt;&lt;/a&gt;, for reasons explained below.&lt;/p&gt;

&lt;p&gt;So, the &lt;code&gt;index.tsx&lt;/code&gt; file looks a little bit like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBlazorApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;piral-blazor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Piral&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SetErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SetLayout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;piral-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMenuApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;piral-menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feedUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://feed.piral.cloud/api/v1/pilet/spiralfy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createInstance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;createBlazorApi&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;//this is where the magic is included ✨&lt;/span&gt;
    &lt;span class="nf"&gt;createMenuApi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;requestPilets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feedUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;piral&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Piral&lt;/span&gt; &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SetLayout&lt;/span&gt; &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SetErrors&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Piral&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;piral&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Things to notice about this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the plugins each come from their respective packages, not from &lt;code&gt;piral&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;in the full Piral framework, we would use &lt;code&gt;renderInstance&lt;/code&gt;. Piral-core however, does not come with react bundled. It means we should use the standard &lt;code&gt;render&lt;/code&gt; method from &lt;code&gt;react-dom&lt;/code&gt; to render our Piral instance. (read more &lt;a href="https://docs.piral.io/guidelines/tutorials/22-core-and-base" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://npmjs.com/piral-blazor" rel="noopener noreferrer"&gt;&lt;code&gt;piral-blazor&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Although I called &lt;code&gt;piral-blazor&lt;/code&gt; a plugin, it is actually considered a &lt;strong&gt;converter&lt;/strong&gt;: a package that brings support to use a different UI framework. Piral supports around 15 different UI frameworks, other than React. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you would like more information on how &lt;code&gt;piral-blazor&lt;/code&gt; works, I would suggest you read the second part of this article, too!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I included the Blazor converter in the app shell, because I already knew I was going to add a Blazor pilet. It's also possible to load &lt;code&gt;piral-blazor&lt;/code&gt; from a pilet. This is for example useful if the app shell already existed and you don't want to change it. This is beyond the scope of this article.&lt;/p&gt;
&lt;h2&gt;
  
  
  The player: a simple pilet
&lt;/h2&gt;

&lt;p&gt;The player pilet is rather barebones. It uses the following npm package, which is neat wrapper around the &lt;a href="https://developer.spotify.com/documentation/web-playback-sdk/" rel="noopener noreferrer"&gt;Spotify web playback SDK&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/gilbarbara" rel="noopener noreferrer"&gt;
        gilbarbara
      &lt;/a&gt; / &lt;a href="https://github.com/gilbarbara/react-spotify-web-playback" rel="noopener noreferrer"&gt;
        react-spotify-web-playback
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A simple player for Spotify's web playback
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;This allows us to have Spotify playback from the browser. If we only used the Spotify API, we could control the playback, but we would have to have an active device it is playing on. This eliminates that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since I wanted to make my own layout for a player in Blazor, I'm setting this one to &lt;code&gt;display:none&lt;/code&gt;. This way it's loaded, but it's also hidden from view. &lt;em&gt;Yes I am aware that this is hacky, stop bullying me! :/&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While the pilet is simple, this is however demonstrating a niche problem that is pretty well solved by the fact that Piral (and microfrontends in general) can be very &lt;strong&gt;technology-independent&lt;/strong&gt;. If you want to write an app in Blazor, but a certain feature has a pretty nice Javascript library already: You can most of the time just use it from a different microfrontend. This way, there's way less need in having to struggle with any JS interop.&lt;/p&gt;

&lt;center&gt;
&lt;a href="https://i.imgur.com/DpDlQP9.jpg" rel="noopener noreferrer"&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FDpDlQP9l.png" width="512" height="640"&gt;
&lt;/a&gt;
&lt;/center&gt;

&lt;h2&gt;
  
  
  The controls: a Blazor pilet!
&lt;/h2&gt;

&lt;p&gt;Now for the interesting bit: the Blazor pilet! &lt;/p&gt;

&lt;p&gt;To create the Blazor pilet, I followed the documentation for &lt;code&gt;Piral.Blazor&lt;/code&gt;, from their &lt;a href="https://github.com/smapiot/Piral.Blazor#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt; on GitHub. &lt;code&gt;Piral.Blazor&lt;/code&gt; is a set of NuGet packages that will make Piral work from the .NET side. &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/smapiot" rel="noopener noreferrer"&gt;
        smapiot
      &lt;/a&gt; / &lt;a href="https://github.com/smapiot/Piral.Blazor" rel="noopener noreferrer"&gt;
        Piral.Blazor
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      All .NET things to make Blazor work seamlessly in microfrontends using Piral. 🧩
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Here is also a quick video on the process, if you don't like reading:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8kWkkNgE3ao"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The process boils down to installing a template. If you want to transform an existing Blazor app however, all you have to do is defining the app shell name, installing the &lt;a href="https://nuget.org/packages/Piral.Blazor.Tools" rel="noopener noreferrer"&gt;&lt;code&gt;Piral.Blazor.Tools&lt;/code&gt;&lt;/a&gt; package that will create the right files for your pilet, and installing &lt;a href="https://nuget.org/packages/Piral.Blazor.Utils" rel="noopener noreferrer"&gt;&lt;code&gt;Piral.Blazor.Utils&lt;/code&gt;&lt;/a&gt; to be able to use custom Piral attributes in your code.&lt;/p&gt;

&lt;p&gt;To make the interaction with the Spotify API a lot easier I used the following great NuGet package, which provides fully typed responses and requests.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/JohnnyCrazy" rel="noopener noreferrer"&gt;
        JohnnyCrazy
      &lt;/a&gt; / &lt;a href="https://github.com/JohnnyCrazy/SpotifyAPI-NET" rel="noopener noreferrer"&gt;
        SpotifyAPI-NET
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🔉 A Client for the Spotify Web API, written in C#/.NET
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Alright, after this round of &lt;em&gt;NuGet installing&lt;/em&gt;, we can talk about the components. &lt;/p&gt;

&lt;p&gt;A standard &lt;strong&gt;page&lt;/strong&gt; in Blazor, using the &lt;code&gt;@page&lt;/code&gt; directive, will work as expected, and will be automatically registered on the pilet API. This is what I used to register the player on the homepage.&lt;/p&gt;

&lt;p&gt;For an &lt;strong&gt;extension&lt;/strong&gt;, like a login button that should end up in the app shell header, we can use the &lt;code&gt;PiralExtension&lt;/code&gt; attribute, specifying the name of the extension slot you want to render into.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@attribute [PiralExtension("header-items")]

@if(_username is null){
    &amp;lt;a href="@_authUri"&amp;gt;Login via Spotify&amp;lt;/a&amp;gt;
}
else
{
    &amp;lt;p&amp;gt;Welcome @_username&amp;lt;/p&amp;gt;
}

@code {
   //...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And... I would say... That's almost the entire story. Because Piral.Blazor does some pretty neat stuff under the hood, the developer experience of creating a Blazor pilet is really similar to creating a regular Blazor WASM application!&lt;/p&gt;
&lt;h3&gt;
  
  
  Let's run it!
&lt;/h3&gt;

&lt;p&gt;Because we are using Piral, running the Blazor pilet does include an extra step. We need to use the Piral CLI to do its magic! Again, the &lt;code&gt;Piral.Blazor&lt;/code&gt; docs to the rescue!&lt;/p&gt;

&lt;p&gt;From your Blazor project folder, run your pilet via the Piral CLI 🚀&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../piral~/&amp;lt;project-name&amp;gt;
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(You could also add a &lt;code&gt;--feed&lt;/code&gt; argument, as outlined &lt;a href="https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46#%E2%96%B8-debugging-one-pilet-and-seeing-the-interaction-with-the-other-pilets-too"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In addition to this, if you want to debug your Blazor pilet using for example Visual Studio, you can just run the pilet with IISExpress. (if you want to use Blazor 3.2, there are extra things to consider. You can read about them &lt;a href="https://github.com/smapiot/Piral.Blazor/tree/blazor-3.2#readme" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;em&gt;The cool kids use Blazor 5 anyway&lt;/em&gt; ⌐■_■ )&lt;/p&gt;

&lt;p&gt;This way, you can really use the entire Blazor debugging experience: hitting breakpoints, stepping through your code, and so on.&lt;/p&gt;




&lt;h1&gt;
  
  
  Dev story: making Blazor work with microfrontends
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Achievement unlocked: you have reached part 2 of the story.&lt;/em&gt; In this part, I wanted to take the opportunity to tell you about how the combination of Piral and Blazor has matured over the period that I was actively contributing to its codebase; working closely together with the maintainers of Piral. &lt;/p&gt;
&lt;h2&gt;
  
  
  Looking back
&lt;/h2&gt;

&lt;p&gt;Now, how was Piral with Blazor organized some 2 or 3 months ago?&lt;/p&gt;


&lt;center&gt;
&lt;a href="https://i.imgur.com/kBlzCzA.png" rel="noopener noreferrer"&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FkBlzCzA.png" width="800" height="450"&gt;The Piral Blazor ecosystem in March, 2021

&lt;/a&gt;
&lt;/center&gt;


&lt;p&gt;This does not look too complicated. Let's break it down.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;Piral instance&lt;/strong&gt;, we rely of course on the Piral framework (although, as said before, this could also be piral-core). Next to that, we also need &lt;code&gt;piral-blazor&lt;/code&gt; as a converter. &lt;/p&gt;

&lt;p&gt;Under the hood, &lt;code&gt;piral-blazor&lt;/code&gt; would actually download the &lt;a href="https://www.nuget.org/packages/Piral.Blazor.Core" rel="noopener noreferrer"&gt;&lt;code&gt;Piral.Blazor.Core&lt;/code&gt;&lt;/a&gt; NuGet package, which would contain &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blazor framework files:  dlls, boot files, metadata, etc.&lt;/li&gt;
&lt;li&gt;custom code that would 

&lt;ul&gt;
&lt;li&gt;expose some methods that can be invoked from the JS side to register, load and unload components&lt;/li&gt;
&lt;li&gt;make sure activated components get rendered in a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with a specific id&lt;/li&gt;
&lt;li&gt;provide a way to register dependencies from the pilets in the DI container &lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;piral-blazor&lt;/code&gt; would include these files in the Piral instance, and could call the exposed methodes using JS interop. This then would allow exposing some functions in the pilet API to allow pilets to activate components from their &lt;code&gt;setup&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;pilets&lt;/strong&gt; would be created using the official template. The template created files that can be divided into 2 categories: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Blazor files&lt;/li&gt;
&lt;li&gt;other files, mainly TS, codegen and JSON files, where the registration with the pilet API was handled (&lt;code&gt;setup&lt;/code&gt; function etc...)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Blazor files can be considered to be the heart of the pilet, while the other files were just there to be the proverbial "glue" that would make everything integrate into the Piral framework.&lt;/p&gt;

&lt;p&gt;Still confused? Below I give an example on how this all worked.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Let's say you know how to create the most beautiful counter component in Blazor. You want to use this counter as a microfrontend in your React application, and give it a dedicated page. Maybe you would also want to render it as an extension on another part of your web app.&lt;/p&gt;

&lt;p&gt;Of course, you are already using Piral. You would add the &lt;code&gt;piral-blazor&lt;/code&gt; converter to the plugins of your Piral instance.&lt;/p&gt;

&lt;p&gt;Then, you would then set up a pilet using &lt;code&gt;Piral.Blazor.Template&lt;/code&gt;, and add your counter component. To expose this component to be able to get picked up by Piral you would add an &lt;code&gt;ExposePilet&lt;/code&gt; attribute. This would look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@attribute [ExposePilet("my-awesome-counter")]     //!

&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;Current count: @count&amp;lt;/p&amp;gt;
    &amp;lt;button @onclick="Increment"&amp;gt;Increment&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;

@code {
    int count = 0;

    void Increment()
    {
        count++;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then you would edit the created &lt;code&gt;index.tsx&lt;/code&gt; file to something like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PiletApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;name-of-piral-instance&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PiletApi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineBlazorReferences&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./My.Dependency.dll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./My.Components.dll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromBlazor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-awesome-counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;//page&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter-slot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromBlazor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-awesome-counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;//extension&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These last lines would register the counter component as a page and an extension via the pilet API. &lt;code&gt;piral-blazor&lt;/code&gt; would then via Piral.Blazor.Core lookup the component in the defined references, activate it using JS invokable methods, and integrate it somewhere in the webpage. &lt;/p&gt;
&lt;h2&gt;
  
  
  What needed improvement, and why?
&lt;/h2&gt;

&lt;p&gt;There are several aspects where the aforementioned ecosystem could improve. Below I outline some of the goals that were set to make this better:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user should be able to select the &lt;strong&gt;version of Blazor&lt;/strong&gt; they want, independently of the &lt;code&gt;piral-blazor&lt;/code&gt; version (because the latter is tied to the Piral version).&lt;/li&gt;
&lt;li&gt;There should be a way to &lt;strong&gt;transform an existing Blazor application&lt;/strong&gt; into a Blazor pilet with minimal effort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registering Blazor dependencies&lt;/strong&gt; is cumbersome and error-prone: they need to be manually entered, and if they are not in the right order they will not load correctly. Ideally, we would have an automatic solution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registering pages and extensions&lt;/strong&gt; onto the pilet API should be possible purely from Blazor, without having to do any TypeScript configuration (in the setup function). Additionally, to define pages we should not use a custom attribute, but use the built-in &lt;code&gt;@page&lt;/code&gt; directive from Blazor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt; a Blazor pilet from for example Visual Studio should be possible: triggering breakpoints, stepping through the code, ...&lt;/li&gt;
&lt;li&gt;Various improvements to single-page navigation, static files, scoped razor styles, ...&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  What has changed and improved?
&lt;/h2&gt;

&lt;p&gt;Let's dive right in by providing an updated diagram of the Piral Blazor ecosystem:&lt;/p&gt;


&lt;center&gt;
&lt;a href="https://i.imgur.com/7KWReas.png" rel="noopener noreferrer"&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F7KWReas.png" width="800" height="450"&gt;The Piral Blazor ecosystem in May, 2021

&lt;/a&gt;
&lt;/center&gt;


&lt;p&gt;Let's go over the improvement goals and see how this new architecture fulfills them: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1. The user should be able to select the &lt;strong&gt;version of Blazor&lt;/strong&gt; they want, independently of the &lt;code&gt;piral-blazor&lt;/code&gt; version (because the latter is tied to the Piral version).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This first goal was made possible by extracting the responsibility of dealing with Blazor files into a new npm package called &lt;code&gt;blazor&lt;/code&gt;, and including it as a peer dependency of &lt;code&gt;piral-blazor&lt;/code&gt;. This way, the user can install a &lt;code&gt;piral-blazor&lt;/code&gt; version that corresponds to their Piral version, and choose any version of the &lt;code&gt;blazor&lt;/code&gt; package to include any version of Blazor (e.g. &lt;code&gt;blazor@3.2.x&lt;/code&gt; will resolve to the .NET Blazor 3.2 release train).&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;2. There should be a way to &lt;strong&gt;transform an existing Blazor application&lt;/strong&gt; into a Blazor pilet with minimal effort.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this goal, we created &lt;a href="https://nuget.org/packages/Piral.Blazor.Tools" rel="noopener noreferrer"&gt;&lt;code&gt;Piral.Blazor.Tools&lt;/code&gt;&lt;/a&gt;. The workflow for transforming a Blazor project into a pilet now looks something like this: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;PiralInstance&lt;/code&gt; property to your &lt;code&gt;.csproj&lt;/code&gt; file with the name of your Piral instance. &lt;/li&gt;
&lt;li&gt;Install the &lt;code&gt;Piral.Blazor.Tools&lt;/code&gt; package&lt;/li&gt;
&lt;li&gt;Build the project. The first time you do this, this can take some time as it will fully scaffold the pilet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;(stuff omitted for brevity. Read the complete description &lt;a href="https://github.com/smapiot/Piral.Blazor#2-transforming-an-existing-blazor-application-into-a-pilet" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the reason the template is now grayed out on the diagram: you don't &lt;em&gt;need&lt;/em&gt; it anymore (but it's still nice to get a quick start)!&lt;/p&gt;

&lt;p&gt;Template or not, the tools package will do all the heavy lifting and create all files needed for integration with the final framework. But this time, they will not be mixed in with the Blazor files: the user should not see these at all! We decided to put them all in a &lt;code&gt;piral~&lt;/code&gt; folder. This final tilde makes it so that this folder will by default not be checked in, thanks to the default .NET &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;3. &lt;strong&gt;Registering Blazor dependencies&lt;/strong&gt; is cumbersome and error-prone: they need to be manually entered, and if they are not in the right order they will not load correctly. Ideally, we would have an automatic solution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tools package allows us to scaffold the pilet, and also copy over any files that we like to the user's pilet. We used this to fix this problem, by letting the tools copy over a &lt;code&gt;blazor.codegen&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;If you are unfamiliar with codegen files, no worries, I was too! They are basically ways to generate code at build-time. Here you can learn more about one of the loaders that Piral can use (this is one for Parcel):&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/FlorianRappl" rel="noopener noreferrer"&gt;
        FlorianRappl
      &lt;/a&gt; / &lt;a href="https://github.com/FlorianRappl/parcel-plugin-codegen" rel="noopener noreferrer"&gt;
        parcel-plugin-codegen
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Parcel plugin for bundle-time code generation. Simple, powerful, and flexible. 📦
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;&lt;code&gt;blazor.codegen&lt;/code&gt; looks for a JSON file called &lt;code&gt;project.assets.json&lt;/code&gt; to build up a dependency graph for the Blazor project, and then traverses/flattens this graph in the right order: dlls without dependencies (=&lt;em&gt;leaves&lt;/em&gt; in the graph) should be loaded first, then their parents, and so on.  &lt;em&gt;(For you algorithm nerds: this is depth-first post-order traversal 🤓 )&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It will then use this ordered list of dependencies to generate the pilet API integration code for us at build-time! (How this works will become clearer together with the next goal)&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;4. &lt;strong&gt;Registering pages and extensions&lt;/strong&gt; onto the pilet API should be possible purely from Blazor, without having to do any TypeScript configuration (in the setup function). Additionally, to define pages we should not use a custom attribute, but use the built-in &lt;code&gt;@page&lt;/code&gt; directive from Blazor. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was an interesting challenge. What has to happen for this to work is that we should have a list of all components that are defined as pages or extensions. And as an additional challenge: we need this list before the components are even loaded, so we cannot simply get them at runtime.&lt;/p&gt;

&lt;p&gt;Luckily, when Blazor gets compiled, this &lt;code&gt;@page&lt;/code&gt; directive just gets converted into a &lt;code&gt;[RouteAttribute]&lt;/code&gt;, and we also have the &lt;code&gt;[PiralExtension]&lt;/code&gt; attribute, so it's all attributes and we can treat these almost in the same way!&lt;/p&gt;

&lt;p&gt;What we came up with, is the &lt;code&gt;Piral.Blazor.Analyzer&lt;/code&gt;: a command line tool that will use reflection on the Blazor project dll to extract all components that have certain attributes. The codegen then calls &lt;code&gt;dotnet Piral.Blazor.Analyzer &amp;lt;args&amp;gt;&lt;/code&gt;; the output of which gives us something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/counter"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"My.Components.Counter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"my-awesome-counter"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The codegen then has all the required information to create code for functions that the setup function needs. The &lt;code&gt;index.tsx&lt;/code&gt; file then will use these generated functions from the codegen, and becomes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PiletApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;name-of-piral-instance&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Blazor&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./blazor.codegen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PiletApi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Blazor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerDependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;Blazor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;Blazor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerExtensions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;blockquote&gt;
&lt;p&gt;5. &lt;strong&gt;Debugging&lt;/strong&gt; a Blazor pilet from for example Visual Studio should be possible: triggering  breakpoints, stepping through the code, ...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was an interesting challenge. The first step for me was learning how the debugger actually works. I stumbled upon an excellent blog post by Safia Abdalla. She's a software engineer at Microsoft on the .NET team.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__37984"&gt;
    &lt;a href="/captainsafia" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F37984%2F46b8bf0f-9fb1-4e3b-bd68-3590ea5737b4.jpeg" alt="captainsafia image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/captainsafia"&gt;Safia Abdalla&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/captainsafia"&gt;I make open source at @nteractio, make software at 
@Microsoft, and write books and blogs. Dream big and follow through even bigger.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The blogpost can be found on her personal blog: &lt;a href="https://safia.rocks/blog/blazor-wasm-debugging/" rel="noopener noreferrer"&gt;&lt;em&gt;Under the hood with debugging in Blazor WebAssembly&lt;/em&gt;&lt;/a&gt;. There, she explains the concept of the debugging proxy.&lt;/p&gt;

&lt;p&gt;After some experiments and proof-of-concept work, we found out that in Blazor 3.2, we could not load the dlls and pdbs (symbol files) dynamically. They had to be included in the &lt;code&gt;blazor.boot.json&lt;/code&gt; file for them to get picked up by the debugger. &lt;br&gt;
We solved this by configuring the Piral CLI using a kras script injector. I won't go into too much detail here, so I'm oversimplifying massively: &lt;a href="https://github.com/florianrappl/kras" rel="noopener noreferrer"&gt;kras&lt;/a&gt; is a proxy/mock server for your frontend. &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/FlorianRappl" rel="noopener noreferrer"&gt;
        FlorianRappl
      &lt;/a&gt; / &lt;a href="https://github.com/FlorianRappl/kras" rel="noopener noreferrer"&gt;
        kras
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Efficient server proxying and mocking in Node.js. 💪
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;While it is most commonly used to mock a backend, we can use it as a proxy too! You can configure kras with js files included in a &lt;code&gt;mocks&lt;/code&gt; folder. Let's take a look at a code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* mocks/debug.js */&lt;/span&gt;

&lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;uniqueAssemblyNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;uniquePdbNames&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldBeProxied&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;toProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/_framework/_bin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shouldBeProxied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iisUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blazor.boot.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;returnWithTweakedBBJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This last part is what we mentioned before: when we debug we want to modify the &lt;code&gt;blazor.boot.json&lt;/code&gt; file to include our pilet dlls and pdbs, so they get loaded when the debug starts.&lt;/p&gt;

&lt;p&gt;As we see on line 5, a request should be proxied if the url points to one of our unique dlls or pdbs. This means the shared dlls from the appshell will just be loaded as normal, but the dlls the Pilet brings will be proxied. On line 9 we can see where to: the requests for the pilet dlls will be proxied to the url where IISExpress is running (we can get this from the &lt;code&gt;launchSettings.json&lt;/code&gt; file by the way).&lt;/p&gt;

&lt;p&gt;Because of this &lt;em&gt;hack&lt;/em&gt;, we can now trigger breakpoints in Visual Studio, step through the code, inspect variables, etc.! &lt;/p&gt;

&lt;p&gt;Oh, by the way, .NET 5 made stuff a lot easier with the inclusion of lazy loading! I won't give a full explanation, but it boils down to: we can now just load the pilet dlls and pdbs lazily, and everything just works! Proxying and tweaking the boot file isn't necessary!&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;6. Various improvements to single-page navigation, static files, scoped razor styles, ...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I won't go into details about these. There are always things to improve about code, and these were some major ones :) &lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;We take the same scenario as before: the counter. With the new setup, if we want a page, there is nothing at all to do that is special. We just need a regular Blazor page. If we also want to expose an extension, we can use the new &lt;code&gt;[PiralExtension]&lt;/code&gt; attribute from &lt;a href="https://nuget.org/packages/Piral.Blazor.Utils" rel="noopener noreferrer"&gt;&lt;code&gt;Piral.Blazor.Utils&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page "/counter"
@attribute [PiralExtension("my-awesome-counter")] 

&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;Current count: @count&amp;lt;/p&amp;gt;
    &amp;lt;button @onclick="Increment"&amp;gt;Increment&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;

@code {
    int count = 0;

    void Increment()
    {
        count++;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tooling will do all the rest! &lt;strong&gt;No more screwing around in TypeScript&lt;/strong&gt; for a setup function to manually create and keep updated! Isn't that convenient?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(For more advanced uses you can still define an optional &lt;code&gt;setup.tsx&lt;/code&gt; file. Refer to the &lt;a href="https://github.com/smapiot/Piral.Blazor#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt; to see how that works.)&lt;/em&gt;&lt;/p&gt;








&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;My first thought after writing this article would be &lt;em&gt;"damn, I wrote quite a long article again"&lt;/em&gt;, immediately followed by &lt;em&gt;"are people actually going to read this one?"&lt;/em&gt;. My last post was received very well, and I thank each and everyone that reacted to it! Feel free to let me know in the comments if this article was in any way interesting, helpful or cool! &lt;/p&gt;

&lt;p&gt;I enjoyed contributing to Piral a lot. I've always wanted to dive into the open-source community, and because of the guidance given by the Piral maintainers, I feel like I could make a difference in this project (Piral is also pretty damn cool if you ask me). &lt;/p&gt;

&lt;p&gt;Looking back on it, in my opinion, using Piral and Blazor has become better in both functionality and developer experience, and I'm really proud of that (&lt;em&gt;"hey, a Belgian guy that says he is proud of something, that's quite rare!"&lt;/em&gt;). If you want to see my contributions first-hand; or just criticize my code, &lt;a href="https://github.com/smapiot/piral/pulls?q=is%3Apr+author%3ADanteDeRuwe" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/smapiot/Piral.Blazor/pulls?q=is%3Apr+author%3ADanteDeRuwe" rel="noopener noreferrer"&gt;here&lt;/a&gt; are lists of the PRs I made. &lt;/p&gt;

&lt;p&gt;Then I want to address Blazor. While I can definitely see the appeal of it, and it's a cool technology: it was pretty hard to get a grasp on the technical side of it. Lots of the stuff that's going on is quite &lt;em&gt;magical&lt;/em&gt; at first. I'm glad the entire thing is open-source, because finding solutions often meant peeking behind the curtain and reading the source files on the &lt;a href="https://github.com/dotnet/aspnetcore" rel="noopener noreferrer"&gt;dotnet/aspnetcore&lt;/a&gt; repo.&lt;/p&gt;

&lt;p&gt;Because of this however, I've learned an awful lot about how Blazor WebAssembly works; what the limitations and weird quirks are, etc. It also just broadened my knowledge of the .NET ecosystem as a whole.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>piral</category>
      <category>microfrontends</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My experiences creating a Netflix clone using microfrontends</title>
      <dc:creator>Dante De Ruwe</dc:creator>
      <pubDate>Thu, 18 Mar 2021 16:46:32 +0000</pubDate>
      <link>https://forem.com/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46</link>
      <guid>https://forem.com/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46</guid>
      <description>&lt;p&gt;I created a Netflix clone using &lt;a href="https://piral.io" rel="noopener noreferrer"&gt;Piral&lt;/a&gt;: an open-source framework for creating modular applications.&lt;/p&gt;

&lt;p&gt;In this article, I will go over what microfrontends are, why they are useful, and what frameworks exist to make implementing them easier. I'll also share my experiences creating a project by myself using React and Piral: two technologies I had previously never touched. I will cover what I did, and how I did it. Finally, I will present some closing thoughts, opinions, and personal notes about this endeavor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The "How I did it" section will be written in a way where every developer, regardless of skill level, should be able to follow. Be sure to give Piral or microfrontends as a whole a try, and let me know how it went!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://netflixclone.deruwe.me" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fwebsite%3Fcolor%3D2b7489%26style%3Dflat-square%26up_message%3Dnetflixclone.deruwe.me%26url%3Dhttps%253A%252F%252Fnetflixclone.deruwe.me" width="92" height="20"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;
&lt;a href="https://netflixclone.deruwe.me" rel="noopener noreferrer"&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FJ63Qkfy.jpg" width="800" height="454"&gt;The home page of the application

&lt;/a&gt;
&lt;/center&gt;

&lt;h1&gt;
  
  
  Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;What are microfrontends?&lt;/li&gt;
&lt;li&gt;Why microfrontends?&lt;/li&gt;
&lt;li&gt;Microfrontend frameworks&lt;/li&gt;
&lt;li&gt;
Piral

&lt;ul&gt;
&lt;li&gt;Building blocks and terminology&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

The project

&lt;ul&gt;
&lt;li&gt;What I did&lt;/li&gt;
&lt;li&gt;How I did it&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Final thoughts&lt;/li&gt;

&lt;li&gt;Quick links to all code&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  What are microfrontends?
&lt;/h1&gt;

&lt;p&gt;Microfrontends try to extend the idea and the benefits of microservices into the frontend space. In essence, this &lt;strong&gt;architecture pattern&lt;/strong&gt; comes down to &lt;em&gt;"splitting up the frontend monolith"&lt;/em&gt; into smaller, more easily manageable pieces. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FTPb2keR.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FTPb2keR.jpeg" width="700" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows fully cross-functional teams to work on these, focussing on a specific business feature or company mission. Rather than "horizontal" teams, per layer or technology; these teams manage the "vertical" slices of the application. Each team is autonomous, and has end-to-end – from the database to the UI – responsibility for the features they develop.&lt;/p&gt;

&lt;p&gt;Teams should be able to independently create and deploy these microfrontends. This cuts down on inter-team communication; which could then also enable &lt;strong&gt;distributed development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is especially beneficial for larger companies and projects, where the Jeff Bezos "Two Pizza Team" rule (i.e. the whole team can be fed by two pizzas) can be helpful. Spotify for example, calls these smaller feature teams "squads".  Interesting read &lt;a href="https://medium.com/pm101/spotify-squad-framework-part-i-8f74bcfcd761" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why microfrontends?
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Microfrontends make teams more agile&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When comparing the characteristics and benefits of microfrontends with the 12 Agile Principles, lots of overlap emerges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Autonomous teams&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Autonomous teams satisfy lots of these agile principles. In short: teams that can operate on their own are less prone to being slowed down, can make changes quickly, and feel a greater sense of ownership.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incremental upgrades&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By being decoupled and decentralized, the microfrontends architecture pattern ensures that the incremental and iterative process of agile software development can succeed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Independent deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Microfrontends can be deployed independently. This can enable shorter release cycles, because all different parts don't have to be in sync with each other.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple and decoupled codebases&lt;/strong&gt;&lt;br&gt;
Simplicity is essential to agility: this makes it easier for the whole team to be on board and iterate fast. Decoupling makes using different technologies possible; but even when using the same technologies throughout the app it can still be very beneficial for efficiency of development.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Microfrontend frameworks
&lt;/h1&gt;

&lt;p&gt;While you could take the microfrontend principles and devise your own solution to manage them (in fact, that's kinda what my bachelor thesis will be about); there are lots of frameworks already out there that can do some of the heavy lifting for you. &lt;/p&gt;

&lt;p&gt;Florian Rappl outlines and categorizes a lot of these frameworks in the following blog post:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/florianrappl/microfrontends-from-zero-to-hero-3be7" class="crayons-story__hidden-navigation-link"&gt;Six Patterns for Microfrontends&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/florianrappl" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F268296%2Feb6a1ad0-17d3-4302-9c44-a263a666b072.jpg" alt="florianrappl profile" class="crayons-avatar__image" width="300" height="300"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/florianrappl" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Florian Rappl
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Florian Rappl
                
              
              &lt;div id="story-author-preview-content-209059" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/florianrappl" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F268296%2Feb6a1ad0-17d3-4302-9c44-a263a666b072.jpg" class="crayons-avatar__image" alt="" width="300" height="300"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Florian Rappl&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/florianrappl/microfrontends-from-zero-to-hero-3be7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 21 '19&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/florianrappl/microfrontends-from-zero-to-hero-3be7" id="article-link-209059"&gt;
          Six Patterns for Microfrontends
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/piral"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;piral&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/microfrontends"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;microfrontends&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/patterns"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;patterns&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/florianrappl/microfrontends-from-zero-to-hero-3be7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;55&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/florianrappl/microfrontends-from-zero-to-hero-3be7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            17 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Popular options include &lt;em&gt;Single SPA&lt;/em&gt;, &lt;em&gt;Open Components&lt;/em&gt;, &lt;em&gt;Mosaic&lt;/em&gt;, &lt;em&gt;Podium&lt;/em&gt;, &lt;em&gt;Luigi&lt;/em&gt; and &lt;em&gt;Piral&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Rather than competing frameworks, most of these exist side by side, and they each provide a different way of creating these microfrontend solutions. They differ in key properties such as completeness (just solving some problems such as routing vs providing a full end-to-end solution with error boundaries, tooling, eco-system, etc.) or architecture style (e.g., build-time composition vs client-side composition vs server-side composition).&lt;/p&gt;

&lt;h1&gt;
  
  
  Piral
&lt;/h1&gt;

&lt;p&gt;Piral is an open-source framework for fully flexible modular applications. It is built on top of React, but has lots of plugins available for other frameworks and technologies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd74r63k60ybs6w5bzs2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd74r63k60ybs6w5bzs2i.png" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building blocks and terminology
&lt;/h2&gt;

&lt;p&gt;An application built with piral consists of multiple parts. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have no experience with microfrontends, this section can be confusing. Don't be alarmed: the section &lt;em&gt;"The project"&lt;/em&gt; below will turn the abstract into the practical, which will be easier to follow. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Pilets (feature modules)
&lt;/h3&gt;

&lt;p&gt;These are the individual feature modules, also known as microfrontends. They each include their own dependencies and assets, and are completely independent of each other. &lt;/p&gt;

&lt;p&gt;Pilets can define how the integration of their components will happen. Does the pilet need a dedicated page, or will the content be rendered inside an already existing pilet? Maybe we need a dedicated page, and also register a button somewhere else that will link to the page? It is all possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  The feed service
&lt;/h3&gt;

&lt;p&gt;Pilets are usually published to a feed service (e.g. a REST API). Piral provides its own feed service over at &lt;a href="https://piral.cloud" rel="noopener noreferrer"&gt;piral.cloud&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It should be noted that Piral can work without a feed service but a feed service makes deployments easy and consumption very dynamic; showcasing all the advantages of Piral.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Piral Instance (app shell)
&lt;/h3&gt;

&lt;p&gt;This is the place where all feature modules will be integrated. The piral instance will pull all registered pilets from the feed service, and put them where they need to go as defined by the pilets themselves. The app shell also is the place to put your basic layout: navbars, headers, footers, and shared components.&lt;/p&gt;

&lt;p&gt;The result of building the app shell is a &lt;code&gt;dist/release&lt;/code&gt; directory for hosting, and a &lt;code&gt;dist/emulator&lt;/code&gt; directory with a tarball which can be published to an NPM registry to aid in the development and the debugging of the individual pilets.&lt;/p&gt;

&lt;h3&gt;
  
  
  (Component) extensions, pages and menu items
&lt;/h3&gt;

&lt;p&gt;The piral API supports registering &lt;em&gt;extensions&lt;/em&gt; in your pilets and Piral instance. Let's say for example we have a webshop with 2 pilets: a &lt;em&gt;discover&lt;/em&gt; pilet that lists products and a &lt;em&gt;checkout&lt;/em&gt; pilet that enables users to purchase these items (this is by the way a classic example for microfrontends, read more &lt;a href="https://micro-frontends.org" rel="noopener noreferrer"&gt;here&lt;/a&gt;). The &lt;em&gt;discover&lt;/em&gt; pilet should include a button to purchase items, but since that is not the responsibility of this team, the &lt;em&gt;checkout&lt;/em&gt; team will create this button and register it as an extension that all pilets can use. The &lt;em&gt;discover&lt;/em&gt; pilet will then just register an extension slot where the app shell will integrate the right extension into.&lt;/p&gt;

&lt;p&gt;Piral also has a built-in way to register pages and menu items. These can also be seen as extensions, but where the work is already done for you.&lt;/p&gt;

&lt;h1&gt;
  
  
  The project
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Application overview
&lt;/h3&gt;

&lt;p&gt;You can find the application online on &lt;a href="https://netflixclone.deruwe.me" rel="noopener noreferrer"&gt;netflixclone.deruwe.me&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This application is a Netflix clone with some basic functionalities. There is a &lt;code&gt;Browse&lt;/code&gt; page where the user can discover showcases of trending series and movies, top-rated ones, etc. &lt;/p&gt;

&lt;p&gt;Of course, to find a specific movie or series, the user can also use the provided &lt;code&gt;Search&lt;/code&gt; bar.&lt;/p&gt;

&lt;p&gt;Every media tile also has a &lt;code&gt;Favorites&lt;/code&gt; toggle in the top right corner. Clicking it adds the series or movies to the user's favorites list, to be found on the favorites page.&lt;/p&gt;

&lt;p&gt;The user can switch accounts via the &lt;code&gt;Profile&lt;/code&gt; option in the top right. All favorites are linked to the specific account.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It is worth noting that this demo project does not come with a custom backend: all data is coming from a 3rd party API, the accounts are dummy accounts, and the favorites are stored in local storage.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Impressions
&lt;/h4&gt;

&lt;center&gt;
&lt;a href="https://media.giphy.com/media/9S16de4Yb3kSi1HMLx/giphy.gif" rel="noopener noreferrer"&gt;
&lt;img src="https://i.giphy.com/media/9S16de4Yb3kSi1HMLx/giphy.gif" width="480" height="270"&gt;
&lt;/a&gt;The &lt;em&gt;Browse&lt;/em&gt; and the &lt;em&gt;Favorites&lt;/em&gt; pages 

&lt;/center&gt;

&lt;center&gt;
&lt;a href="https://media.giphy.com/media/rOa1PlSAnA4hgHV7z3/giphy.gif" rel="noopener noreferrer"&gt;
&lt;img src="https://i.giphy.com/media/rOa1PlSAnA4hgHV7z3/giphy.gif" width="480" height="270"&gt;
&lt;/a&gt;The &lt;em&gt;Profile&lt;/em&gt; page 

&lt;/center&gt;

&lt;h3&gt;
  
  
  Structure of the application
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j8k5tl0d72k02amep1r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j8k5tl0d72k02amep1r.jpg" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The app shell
&lt;/h4&gt;

&lt;p&gt;The app shell contains only the logo, navigation, and footer. All the other components are provided by the pilets in the form of extensions, pages, and menu items.&lt;/p&gt;

&lt;h4&gt;
  
  
  The pilets
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pilet&lt;/th&gt;
&lt;th&gt;Registered components&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Browse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;li&gt;Menu item&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Browse&lt;/code&gt; (page)&lt;/li&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Favorites&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;li&gt;Menu item&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FavoritesToggle&lt;/code&gt; (component extension)&lt;/li&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Watch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;li&gt;
&lt;code&gt;MovieTile&lt;/code&gt; (component extension)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Player&lt;/code&gt; (page)&lt;/li&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;li&gt;
&lt;code&gt;Search&lt;/code&gt; (component extension)&lt;/li&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Profile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;li&gt;
&lt;code&gt;UserProfile&lt;/code&gt; (component extension)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AccountSwitcher&lt;/code&gt; (page)&lt;/li&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  How I did it
&lt;/h2&gt;

&lt;p&gt;Throughout the creation of the project using piral, obviously, the &lt;a href="https://docs.piral.io" rel="noopener noreferrer"&gt;Piral documentation&lt;/a&gt; was my main source of inspiration. There, they also have video tutorials on lots of topics regarding Piral.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.piral.io" rel="noopener noreferrer"&gt;Piral documentation&lt;/a&gt; also talks about the &lt;a href="https://docs.piral.io/guidelines/tutorials/07-development-workflow" rel="noopener noreferrer"&gt;3 phases of the development workflow&lt;/a&gt;. This is also the way I tried to develop my application. Of course, to be able to experiment, I sometimes stepped a bit of out bounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  0. The planning phase
&lt;/h3&gt;

&lt;p&gt;But before following any of the laid-out steps provided by Piral, I looked out for a sample project that I could build upon. I'm not a designer, so looking for a React project with good styling was the easiest option. I found &lt;a href="https://github.com/fullstackreact/react-daily-ui/tree/master/003-landing-page" rel="noopener noreferrer"&gt;this project&lt;/a&gt;, which was written using an older React syntax, and was all in one big &lt;code&gt;App&lt;/code&gt; module. I converted everything into separate React functional components. This was a great way to learn how React works.&lt;/p&gt;

&lt;p&gt;You can see the results in the following repo. The commit history here shows what I did.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/react-netflix-clone" rel="noopener noreferrer"&gt;
        react-netflix-clone
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Learning React by creating a simple Netflix clone. (I transformed this into a microfrontend solution! See https://git.io/netflix-piral)
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  1. The setup phase
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;What needs to be done in this phase? &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Develop the piral instance&lt;/li&gt;
&lt;li&gt;Set up a feed service and connect the piral instance to it&lt;/li&gt;
&lt;li&gt;Distribute an emulator package&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  1.1. Creating the Piral instance (app shell)
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the code &lt;a href="https://github.com/DanteDeRuwe/netflix-piral" rel="noopener noreferrer"&gt;on github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Following the documentation showed me how to get this up and running. Install the Piral CLI globally by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;piral-cli &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(one could also use npx to avoid unsafe global installations, see below)&lt;/p&gt;

&lt;p&gt;The CLI exposes commands starting with &lt;code&gt;piral&lt;/code&gt; and &lt;code&gt;pilet&lt;/code&gt;. In this phase, of course, we will need the &lt;code&gt;piral&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;To create a Piral instance (app shell) called &lt;em&gt;netflix-piral&lt;/em&gt;, let's run&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;piral new &lt;span class="nt"&gt;--target&lt;/span&gt; netflix-piral
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can run the newly created Piral instance with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;piral debug

&lt;span class="c"&gt;# or, if you want to open the browser automatically:&lt;/span&gt;
piral debug &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;– using &lt;code&gt;npx&lt;/code&gt; –&lt;/p&gt;

&lt;p&gt;Instead of a command like &lt;code&gt;piral debug&lt;/code&gt; which only works inside a directory with a &lt;code&gt;package.json&lt;/code&gt; or if you have the Piral CLI installed globally, you can also use &lt;code&gt;npx&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if the given name is not available in (modified) path (e.g., global path or &lt;code&gt;node_modules/.bin&lt;/code&gt;), npx will try to get the command from NPM and run it (non-globally i.e. from user privileges)&lt;/li&gt;
&lt;li&gt;if the given name &lt;em&gt;is&lt;/em&gt; available it will just run it from there (also from user privileges)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's have a look at one of the most important files, &lt;code&gt;index.tsx&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;renderInstance&lt;/code&gt; function outlines the responsibilities of the app shell: it takes care of the layout, the error layout, and requests the pilets from a feed service. As we can see on line 6 - by default - it's just pulling from an empty feed.&lt;/p&gt;

&lt;p&gt;In fact, the only thing that will change in this file, is the feed URL. To be able to do that: let's first set up a feed.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.2. Setting up the feed service
&lt;/h4&gt;

&lt;p&gt;While you could (and in some circumstances, should) set up your own feed service, most of the time the service provided by the Piral team itself will suffice. For development purposes, you get multiple feeds for free! This service can be found on &lt;a href="https://piral.cloud" rel="noopener noreferrer"&gt;piral.cloud&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  ▸ Creating a feed on &lt;a href="https://piral.cloud" rel="noopener noreferrer"&gt;piral.cloud&lt;/a&gt;
&lt;/h5&gt;

&lt;p&gt;Sign in to the service and you'll see the following screen&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo79h6palu54ytg3nmsry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo79h6palu54ytg3nmsry.png" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, we're going to click &lt;code&gt;+ New Feed&lt;/code&gt;.&lt;br&gt;
Next, we'll give the feed a unique name (which cannot be changed), and optionally, a description so it's clear for what this feed will be used.&lt;br&gt;
You can also configure the allowed hosts.&lt;/p&gt;

&lt;p&gt;You'll see the result on the overview: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01z5waocpo4rgzt2gab0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01z5waocpo4rgzt2gab0.png" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To be able to publish pilets later, we'll need an api key. You can manage them by clicking
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzkqx4w7ut8jxlgv5rzp.png" width="271" height="66"&gt;
&lt;/li&gt;
&lt;li&gt;To get the feed url for the app shell, we can click the feed title. The url will be displayed:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FWmFx0mC.png" height="705" width="800"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll copy the feed url and place it where we wanted it before: in the &lt;code&gt;index.tsx&lt;/code&gt; of the Piral instance (line 6).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h4&gt;
  
  
  1.3. Creating the app shell layout
&lt;/h4&gt;

&lt;p&gt;We have an app shell now which pulls from our own (still empty) feed! We'll add pilets to this feed later. But first, maybe we should customize the layout of this app shell. As written before, the main responsibilities we want for this app shell are the logo, the navigation, and the footer. &lt;/p&gt;

&lt;p&gt;After scaffolding, the &lt;code&gt;layout.tsx&lt;/code&gt; file contains a lot of components and also combines them in a &lt;code&gt;layout&lt;/code&gt; object to be used by the &lt;code&gt;index.tsx&lt;/code&gt;. While this is fine, I like to split up all my components using a single file per component, so the result looks like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We'll put the layout in &lt;code&gt;./components/App.tsx&lt;/code&gt;, the navigation template in &lt;code&gt;.components/Navigation.tsx&lt;/code&gt; and for the menuitems, they are just rendered using &lt;code&gt;&amp;lt;li&amp;gt;...&amp;lt;/li&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Remember what I mentioned before:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The app shell contains only the logo, navigation, and footer. All the other components are provided by the pilets in the form of extensions, pages, and menu items.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is absolutely the case, but we do however need to define where the pilets need to render these extensions! Here is a quick wireframe diagram for the app shell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FWOAJiHa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FWOAJiHa.png" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;pages&lt;/strong&gt; registered by the pilets will just be given to the &lt;code&gt;App&lt;/code&gt; component as &lt;code&gt;children&lt;/code&gt;. We can use a react-router to surround them.&lt;/p&gt;

&lt;p&gt;As for the &lt;strong&gt;extensions&lt;/strong&gt;: The key to being able to integrate these is an &lt;code&gt;ExtensionSlot&lt;/code&gt; with a specific name. The pilets are then able to register extensions, providing a name, and the app shell will put them in the right slot.&lt;/p&gt;

&lt;p&gt;The code for the &lt;code&gt;App&lt;/code&gt; component is below. On line 14 the extension slot with &lt;code&gt;name="header-items"&lt;/code&gt; is registered, on line 19, the different pages will be rendered.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;strong&gt;menu items&lt;/strong&gt; are standardized in Piral. The component registered in &lt;code&gt;index.tsx&lt;/code&gt; as the &lt;code&gt;MenuContainer&lt;/code&gt; (= in our case, the &lt;code&gt;Navigation&lt;/code&gt; component) will get menu items as &lt;code&gt;children&lt;/code&gt; when pilets register them. &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h4&gt;
  
  
  1.4. Deploying the app shell on Netlify (or somewhere else)
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;If you already know how hosting works, here's a TLDR: execute &lt;code&gt;piral build --type release&lt;/code&gt; and publish the &lt;code&gt;dist/release/&lt;/code&gt; folder! You could of course set up CI/CD to do this for you. Don't forget a &lt;code&gt;_redirects&lt;/code&gt; file for routing!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To deploy the application for the world to see, we need to publish it somewhere. To me, the best place to do this is &lt;a href="https://netlify.com" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;. One could of course choose Azure Static Web Apps, Github pages, or another hosting platform, but Netlify is easy to use and has a lot of great features that are completely free.&lt;/p&gt;

&lt;p&gt;To get started, create an account on Netlify. I like to use my Github account because this way the accounts are already linked.&lt;/p&gt;

&lt;p&gt;Next, create a "New site from git" in the &lt;em&gt;sites&lt;/em&gt; tab of the interface. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6ijox9gyzvtmhl6htby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6ijox9gyzvtmhl6htby.png" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Find the Github repository of your app shell. If you don't have one already... you should create one ;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ps7qrv501q23cqxni3z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ps7qrv501q23cqxni3z.png" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now configure the build settings as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;set a branch (I use master, you could also create a custom &lt;code&gt;release&lt;/code&gt; branch)&lt;/li&gt;
&lt;li&gt;set the build command to &lt;code&gt;npm run build&lt;/code&gt; or &lt;code&gt;piral build&lt;/code&gt; or &lt;code&gt;piral build --type release&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;set the publish directory to &lt;code&gt;/dist/release/&lt;/code&gt; (don't skip this step!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrayp8h7cq3gtqed3yxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrayp8h7cq3gtqed3yxa.png" width="733" height="721"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you are ready to deploy your site with the click of a button! Now every time you push your code to the selected branch, the site will be updated! CI/CD for the win!&lt;/p&gt;

&lt;h5&gt;
  
  
  ▸ The &lt;code&gt;_redirects&lt;/code&gt; file
&lt;/h5&gt;

&lt;p&gt;When you deploy the app shell for the first time, you will not notice it, but the routing is not perfect. To save yourselves some headaches later on, you best follow the next steps already, so you won't have to touch your app shell again.&lt;/p&gt;

&lt;p&gt;If you go to &lt;em&gt;yourwebsite.netlify.app/test&lt;/em&gt;, Netlify will try to find a &lt;code&gt;test.html&lt;/code&gt; page to serve you, will not find it, and show an error message. We want React Router to deal with routes. We have to redirect all routes to the &lt;code&gt;index.html&lt;/code&gt;... To do this, we create a folder with path &lt;code&gt;/src/static/&lt;/code&gt; and put a &lt;code&gt;_redirects&lt;/code&gt; file into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* /index.html  200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To make sure this file is copied to the &lt;code&gt;release&lt;/code&gt; directory on build, we need to configure webpack to do so. &lt;/p&gt;

&lt;p&gt;Install the &lt;a href="https://webpack.js.org/plugins/copy-webpack-plugin/" rel="noopener noreferrer"&gt;CopyWebpackPlugin&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;copy-webpack-plugin &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the root folder of your project, create &lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This will copy everything from the &lt;code&gt;src/static/&lt;/code&gt; directory to the build directory. This means you can later on also add images and other files to this &lt;code&gt;static&lt;/code&gt; directory if you so desire.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.5. Publishing the emulator
&lt;/h4&gt;

&lt;h5&gt;
  
  
  ▸ What is the purpose of the emulator?
&lt;/h5&gt;

&lt;p&gt;Now, we have our app shell up and running. When pushing Pilets to our feed service, the app shell can access these immediately and the site will be updated. But what if we want to develop new pilets? Surely we won't be publishing them a hundred times to see how they look, right?&lt;/p&gt;

&lt;p&gt;Luckily, Piral has a good solution to this: an app shell &lt;strong&gt;emulator&lt;/strong&gt;. The pilets can use the emulator to see how they will look when integrated into the app shell, to be able to quickly debug the pilets.&lt;/p&gt;

&lt;p&gt;To create an app shell emulator, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;piral build &lt;span class="nt"&gt;--type&lt;/span&gt; emulator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The emulator is a &lt;code&gt;.tar.gz&lt;/code&gt; or &lt;code&gt;.tgz&lt;/code&gt; file (a so-called "tarball") and can be found in the &lt;code&gt;/dist/emulator/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Great. Now we have a file. If we are creating pilets alone, on one pc, this is no big deal. But ideally, we want the emulator to be accessible from every pilet, and also be able to update the emulator when a new version of the app shell is necessary. That's why it makes sense to publish the emulator.&lt;/p&gt;
&lt;h5&gt;
  
  
  ▸ publishing the emulator package to npm
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have experience with npm, here's a TLDR: run &lt;code&gt;npm publish dist/emulator/&amp;lt;emulator_file&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To be able to access the emulator from everywhere, we are going to use the node package manager or npm.  First, go to &lt;a href="https://npmjs.org" rel="noopener noreferrer"&gt;npmjs.org&lt;/a&gt; and create an account if you don't already have one.&lt;/p&gt;

&lt;p&gt;Next, in your terminal, run&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and log in using your username and password. Next, you can run&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm publish dist/emulator/&amp;lt;emulator_file&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;&amp;lt;emulator_file&amp;gt;&lt;/code&gt; will in our case be &lt;code&gt;netflix-piral-1.0.0.tgz&lt;/code&gt; or something similar. If you get an error (which could mean the name you chose is already taken), refer to &lt;a href="https://zellwk.com/blog/publish-to-npm/" rel="noopener noreferrer"&gt;this article&lt;/a&gt; or the &lt;a href="https://docs.npmjs.com/" rel="noopener noreferrer"&gt;npm docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you look at your registered packages on npmjs.org, you should be able to see the published emulator package! This will be very useful in the next phase: &lt;em&gt;the feature phase&lt;/em&gt;, where the development of the pilets will be addressed.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. The feature phase
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;What needs to be done in this phase? &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build and publish pilets to enable functionalities in the app.&lt;/li&gt;
&lt;li&gt;Manage separation of concerns

&lt;ul&gt;
&lt;li&gt;extract app shell functionality into pilets&lt;/li&gt;
&lt;li&gt;split larger pilets or merge smaller ones&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  2.1 Scaffolding a pilet
&lt;/h4&gt;

&lt;p&gt;Creating a pilet is really straightforward. The piral CLI provides an easy way to scaffold a pilet based on a piral instance. For us the workflow will look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;browse
&lt;span class="nb"&gt;cd &lt;/span&gt;browse
pilet new netflix-piral
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will create a folder &lt;code&gt;browse&lt;/code&gt;, and put a new pilet called "browse" – which is based on the Piral instance &lt;code&gt;netflix-piral&lt;/code&gt; – inside of it.&lt;/p&gt;
&lt;h4&gt;
  
  
  2.2 The first version of the &lt;a href="https://github.com/DanteDeRuwe/netflix-browse-pilet" rel="noopener noreferrer"&gt;&lt;code&gt;browse&lt;/code&gt;&lt;/a&gt; pilet
&lt;/h4&gt;

&lt;p&gt;Let's create some functionalities! The home page of this app will be the "browse" page. Since discovering new series and letting the user browse through series and movies is a pretty big part of the app, this will be the responsibility of one pilet (and, by consequence, a separate dev team).&lt;/p&gt;

&lt;p&gt;The file structure looks like this: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4js3agc94jg2305rbuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4js3agc94jg2305rbuv.png" width="338" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A pilet is very lightweight. The only file to look at is the &lt;code&gt;index.tsx&lt;/code&gt;, where some interesting examples of the Piral API are shown:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The setup function is the heart of the pilet. This is where the app shell will look for instructions for integrations. &lt;/p&gt;

&lt;p&gt;We won't need the notifications or the tiles. You can learn more on these from the Piral documentation.&lt;/p&gt;

&lt;p&gt;The most interesting method for us is &lt;code&gt;registerMenu&lt;/code&gt;, we'll need this for the "Browse" menu item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerMenu&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/browse"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Browse&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And to register a page where this menu item can link to, we will need to add&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/browse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Browse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Where this &lt;code&gt;Browse&lt;/code&gt; is just a regular React component (for now). The structure looks a bit like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Browse
    ├── Hero
    ├── Showcase
    │       ├── MovieTile
    │       └── ... &lt;span class="c"&gt;#more movietiles&lt;/span&gt;
    └── ... &lt;span class="c"&gt;#more showcases with movietiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  ▸ Debugging the pilet in isolation
&lt;/h5&gt;

&lt;p&gt;To be able to test how the pilet will look after integration into the app shell, of course, we could just publish it and look at the live website. However, I won't have to explain why "testing in production" is not the best idea ever.&lt;/p&gt;

&lt;p&gt;So, Piral offers a way to debug the pilet, this is where the emulator comes into play. To debug the pilet, you can run&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pilet debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After the build process is complete, the CLI will let you know on what local address you can look at the result (usually &lt;a href="http://localhost:1234" rel="noopener noreferrer"&gt;http://localhost:1234&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It's interesting to note that this command is almost identical to the one for the app shell, but there we used the &lt;code&gt;piral&lt;/code&gt; keyword, and now we use &lt;code&gt;pilet&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This section is called &lt;em&gt;"debugging the pilet in isolation"&lt;/em&gt;, which seems logical since we only have one pilet defined. Later on, I'll discuss a great feature that enables you to debug one pilet that is part of an application with multiple pilets.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;
  
  
  ▸ Publishing the pilet
&lt;/h5&gt;

&lt;p&gt;We already published the piral instance (app shell), and the fun thing about working with Piral is that this app shell will pull every pilet from a feed and integrate them client-side.&lt;/p&gt;

&lt;p&gt;This means, to publish a pilet, we won't have to touch deployment stuff. We just need to publish the pilet to the feed we created earlier.&lt;/p&gt;

&lt;p&gt;We can do this by:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pilet publish &lt;span class="nt"&gt;--fresh&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; &amp;lt;feed_url&amp;gt; &lt;span class="nt"&gt;---api-key&lt;/span&gt; &amp;lt;feed_api_key&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Tip: I saved this snippet as a script called &lt;code&gt;publish.sh&lt;/code&gt;, added it to &lt;code&gt;.gitignore&lt;/code&gt; (so my API key won't be on Github), and then ran the script whenever I wanted to publish.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;--fresh&lt;/code&gt; flag makes sure that before publishing, a fresh build is made to include any changes made after the last build.&lt;/p&gt;

&lt;p&gt;The feed url and API key, as mentioned before, you can find in the piral feed service dashboard. The direct url is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.piral.cloud/feeds/&amp;lt;feed_name&amp;gt;/api-keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  2.3 The &lt;a href="https://github.com/DanteDeRuwe/netflix-profile-pilet" rel="noopener noreferrer"&gt;&lt;code&gt;profile&lt;/code&gt;&lt;/a&gt; pilet
&lt;/h4&gt;

&lt;p&gt;Next, let's tackle a more interesting case. The &lt;code&gt;profile&lt;/code&gt; pilet. This pilet will again register a page, &lt;code&gt;/profile&lt;/code&gt;, but will also do something else: it will register a &lt;strong&gt;component extension&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FeSx7Sx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FeSx7Sx3.png" height="361" width="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When looking back at the app shell, this component extension has to be put in the extension slot &lt;code&gt;header-items&lt;/code&gt;. so that's what we will do.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;index.tsx&lt;/code&gt; of the profile pilet will then look like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Where &lt;a href="https://github.com/DanteDeRuwe/netflix-profile-pilet/blob/master/src/components/ProfileExtension.tsx" rel="noopener noreferrer"&gt;&lt;code&gt;ProfileExtension&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/DanteDeRuwe/netflix-profile-pilet/blob/master/src/components/ProfilePage.tsx" rel="noopener noreferrer"&gt;&lt;code&gt;ProfilePage&lt;/code&gt;&lt;/a&gt; are just regular React components.&lt;/p&gt;

&lt;p&gt;As is the deal with component extensions: the app shell will integrate the registered extension into the right &lt;code&gt;ExtensionSlot&lt;/code&gt; (the one with a matching name).&lt;/p&gt;

&lt;h4&gt;
  
  
  2.4 The &lt;a href="https://github.com/DanteDeRuwe/netflix-favorites-pilet" rel="noopener noreferrer"&gt;&lt;code&gt;favorites&lt;/code&gt;&lt;/a&gt; pilet
&lt;/h4&gt;

&lt;p&gt;Here we start to run into an interesting thing. We want to introduce the favorites as a page where we can find all the favorite series or movies. This means multiple things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Just like in the Browse component, we will need a way of displaying media (&lt;code&gt;MovieTile&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;We will need to provide a &lt;code&gt;FavoritesToggle&lt;/code&gt; button in every &lt;code&gt;MovieTile&lt;/code&gt;, to be able to toggle this item as a favorite&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  ▸ The &lt;code&gt;MovieTile&lt;/code&gt; and thoughts about code duplication
&lt;/h5&gt;

&lt;p&gt;We could just copy over the &lt;code&gt;MovieTile&lt;/code&gt; code from the browse component and reuse it here. This would be a very viable strategy, and it's also the one I used when you look back in the commit history. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;– &lt;em&gt;"Don't repeat yourself"&lt;/em&gt; ? –&lt;br&gt;
While it is true that the DRY principle can result in cleaner code within the scope of one solution; it sometimes limits the desired decoupling of applications. Especially in microfrontends, sometimes repeating yourself cán be useful, and the reverse is often more difficult and undesirable. There is an interesting article to be read &lt;a href="https://medium.com/swlh/the-case-against-dry-micro-frontends-edition-7d6657685f52" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That being said, later on in the project, I looked back at this part in the Piral docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Determine when to split pilets and potentially split larger pilets or merge smaller ones."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's when it started making sense to extract the &lt;code&gt;MovieTiles&lt;/code&gt; into a separate &lt;code&gt;watch&lt;/code&gt; pilet, where they are registered as component extensions. I'll talk about the watch pilet in the next section.&lt;/p&gt;

&lt;h5&gt;
  
  
  ▸ The &lt;code&gt;FavoritesToggle&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;We'll offer the favorites button as a component extension, so all pilets or the app shell can integrate this button wherever they want them.&lt;/p&gt;

&lt;p&gt;For that, we need this in the &lt;code&gt;setup&lt;/code&gt; function of the favorites pilet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ListToggle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FavoriteToggle&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;FavoriteToggle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is where &lt;em&gt;passing parameters into component extensions&lt;/em&gt; becomes interesting. A very basic &lt;code&gt;FavoriteToggle&lt;/code&gt; component may look like this: &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;(If you want to see the full code, check the &lt;a href="https://github.com/DanteDeRuwe/netflix-favorites-pilet" rel="noopener noreferrer"&gt;github repo&lt;/a&gt;, I'm omitting stuff for brevity)&lt;/p&gt;

&lt;p&gt;For this toggle function, it is important that the button gets some properties. Using a favourites toggle may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FavoritesToggle&lt;/span&gt; &lt;span class="na"&gt;movieId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"15165"&lt;/span&gt; &lt;span class="na"&gt;media_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tv"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;or something similar. All this introduction leads us to the main question: &lt;em&gt;how to pass parameters to component extensions when using them across pilets?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Well, it's pretty easy: the &lt;code&gt;Extensionslot&lt;/code&gt; component has a property &lt;code&gt;params&lt;/code&gt;. Whenever we want to use an extension, we give the slot the params, and piral will pass these params to the extension that will end up in that slot. This means, a registered extension will have &lt;code&gt;props.params&lt;/code&gt;, which will come from wherever we defined the extension slot.&lt;/p&gt;

&lt;p&gt;If we want to use this component extension from other pilets, the extension slot will have to look something like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ExtensionSlot&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ListToggle"&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/*an object with the params here*/&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We will see an example and best practices about this in the next section:&lt;/p&gt;
&lt;h4&gt;
  
  
  2.5 the &lt;a href="https://github.com/DanteDeRuwe/netflix-watch-pilet" rel="noopener noreferrer"&gt;&lt;code&gt;watch&lt;/code&gt;&lt;/a&gt; pilet
&lt;/h4&gt;

&lt;p&gt;This pilet would have 2 things registered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;MovieTile&lt;/code&gt; we talked about earlier.

&lt;ul&gt;
&lt;li&gt;this should have a spot where our &lt;code&gt;FavoritesToggle&lt;/code&gt; component extension can fit into!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;Player&lt;/code&gt; (which is just a simple page and we won't discuss further)&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;
  
  
  ▸ The MovieTile
&lt;/h5&gt;

&lt;p&gt;This was an interesting lesson in what I like to call &lt;em&gt;extensionception&lt;/em&gt;: we'll register a component extension, but within that extension, we'll use an &lt;code&gt;ExtensionSlot&lt;/code&gt; where another component extension will fit into:&lt;/p&gt;

&lt;p&gt;The eventual result on for example the favorites page will look like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakwbx18hvlt85aptxzjb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakwbx18hvlt85aptxzjb.png" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, let's look at the &lt;code&gt;MovieTile&lt;/code&gt; component:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This component accepts a whole bunch of properties to be able to display the movie tiles with all the information. It's a purely presentational component.&lt;/p&gt;

&lt;h5&gt;
  
  
  ▸ Passing extension dependencies via props
&lt;/h5&gt;

&lt;p&gt;On line 11 you can see that the &lt;code&gt;MovieTileProps&lt;/code&gt; also contain a definition for a React component reference: this will be the &lt;code&gt;FavoritesToggle&lt;/code&gt; we defined before.&lt;/p&gt;

&lt;p&gt;But why don't we just put &lt;code&gt;&amp;lt;Extensionslot name="ListToggle"/&amp;gt;&lt;/code&gt; there? Well, it's because of what I learned while reading the &lt;a href="https://docs.piral.io/guidelines/tutorials/09-pilet-best-practices#using-extensions" rel="noopener noreferrer"&gt;&lt;em&gt;Pilet best practices&lt;/em&gt; on using extensions&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Using components provided from other pilets is done via "extensions". The problem is that the extensions require the Extension component of the Pilet API to be integrated.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Indeed. We would need to do this at the top of our component extension file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExtensionSlot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;piral&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a bad practice: we couple our components to the Pilet API, and now they are no longer reusable, testable, and generic.&lt;/p&gt;

&lt;p&gt;The fix comes down to this: The only file in a pilet that should depend on the Piral framework is the &lt;code&gt;index.tsx&lt;/code&gt; file with the &lt;code&gt;setup&lt;/code&gt; function. From there, we can pass down the needed dependencies. For the &lt;code&gt;MovieTile&lt;/code&gt;s, it looks like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;On line 10, we use &lt;code&gt;app.Extension&lt;/code&gt;, which has the same purpose as an &lt;code&gt;ExtensionSlot&lt;/code&gt;. We use the result of that to have a component to pass into another component. This way, the &lt;code&gt;MovieTile&lt;/code&gt; has &lt;code&gt;props.Toggle&lt;/code&gt; defined, and can use it just like any other React component.&lt;/p&gt;

&lt;h5&gt;
  
  
  ▸ Debugging one pilet and seeing the interaction with the other pilets too
&lt;/h5&gt;

&lt;p&gt;While developing the &lt;code&gt;browse&lt;/code&gt; pilet, the section where I talked about debugging was called &lt;em&gt;"debugging the pilet in isolation"&lt;/em&gt;. Now, we're going to do something more powerful.&lt;/p&gt;

&lt;p&gt;Let's recall what happens when we run &lt;code&gt;pilet debug&lt;/code&gt;. We have an app shell emulator in which the pilet will be integrated. That's it – 2 parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the app shell (emulated)&lt;/li&gt;
&lt;li&gt;the pilet that's being debugged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But what if we want to see the already published pilets too, to see how the pilet that we are debugging will fit into them? (mainly, in this case, we want to see how the extensions integrate)&lt;/p&gt;

&lt;p&gt;At the time of writing, the Piral CLI is still in version &lt;code&gt;v0.12.4&lt;/code&gt;, but I got the recommendation to switch to the &lt;code&gt;v1.0.0&lt;/code&gt; preview version (the &lt;code&gt;@next&lt;/code&gt; version). This version of the CLI provides in my opinion a major game-changing feature: the ability to debug pilets, while also being able to include remote pilets from the feed!&lt;/p&gt;

&lt;p&gt;It's also very easy to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pilet debug &lt;span class="nt"&gt;--feed&lt;/span&gt; &amp;lt;feed_url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And voila! We can see how the new pilet will fit into the app shell and the already defined pilets in the feed! Amazing!&lt;/p&gt;

&lt;p&gt;Honestly, since learning about this feature, I never ever used debugging in isolation again. It's so much easier to see how the pilet will fit into the application when also including other pilets into view.&lt;/p&gt;

&lt;p&gt;To make my life easier, this is what my &lt;code&gt;scripts&lt;/code&gt; looked like in every pilet's &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pilet debug --feed &amp;lt;feed_url&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This way, I could just run the command &lt;code&gt;npm run debug&lt;/code&gt;!&lt;/p&gt;
&lt;h4&gt;
  
  
  2.6 The &lt;a href="https://github.com/DanteDeRuwe/netflix-search-pilet" rel="noopener noreferrer"&gt;&lt;code&gt;search&lt;/code&gt;&lt;/a&gt; pilet
&lt;/h4&gt;

&lt;p&gt;This pilet just registers one component extension. We'll also set it to render into the &lt;code&gt;header-items&lt;/code&gt; slot. This way: we will get the search and the profile extension both in there.&lt;/p&gt;


&lt;h3&gt;
  
  
  3. The maintenance phase
&lt;/h3&gt;

&lt;p&gt;This is mainly bug fixing and doing optimizations.&lt;/p&gt;
&lt;h4&gt;
  
  
  Persisted state
&lt;/h4&gt;

&lt;p&gt;This has nothing to do with Piral, but I wanted to store some data via local storage and I ran into a pretty cool way to do this by using &lt;a href="https://github.com/donavon/use-persisted-state" rel="noopener noreferrer"&gt;this custom react hook&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Lazy loading
&lt;/h4&gt;

&lt;p&gt;In the pilet setup function, we can set pages to lazily load. This is related to bundle splitting: more info &lt;a href="https://docs.piral.io/guidelines/tutorials/09-pilet-best-practices#bundle-splitting" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;e.g.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProfilePage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/ProfilePage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ProfilePage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Making changes to the app shell
&lt;/h4&gt;

&lt;p&gt;If time was spent thinking about the responsibilities of the app shell before developing the first pilets, you can save yourself a lot of headaches. Though it is possible that the app shell needs to be updated. Of course, the pilets that depend on the app shell emulator for debugging would need to get an update as well!&lt;/p&gt;

&lt;p&gt;Luckily, this is fairly simple&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the app shell is updated, built, and the update is pushed to npm&lt;/li&gt;
&lt;li&gt;in the pilet, run &lt;code&gt;pilet upgrade&lt;/code&gt; to pull in the latest version of the emulator&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;While I had 0 experience using React and Piral before doing this project, I think the project turned out really well. &lt;/p&gt;

&lt;p&gt;When working with microfrontends, the biggest hurdle is getting to the big picture. To me, it was really complicated to imagine how all the microfrontends would come together.&lt;/p&gt;
&lt;h5&gt;
  
  
  ▸ The "black box method" for learning concepts
&lt;/h5&gt;

&lt;p&gt;I saw &lt;a href="https://www.youtube.com/watch?v=RDzsrmMl48I" rel="noopener noreferrer"&gt;this video&lt;/a&gt; recently and it really stuck with me. When trying to understand hard concepts: treat them like a black box first, and learn how to &lt;em&gt;use&lt;/em&gt; them, before learning about how they work.&lt;/p&gt;

&lt;p&gt;The experience you get by using a concept will give you a major advantage while learning how they work because you will already understand the desired outcome.&lt;/p&gt;

&lt;p&gt;The key to understanding microfrontends – in my opinion – is to build some! Once you see visually how they all come together, it's easier to imagine how this integration is happening. This is why a microfrontend framework is valuable. Not only does it provide the best developer experience, but also: lots of stuff is already done for you, and you can get started easily.&lt;/p&gt;

&lt;p&gt;This analogy, by the way, also makes sense when explaining how I learned to work with React in just one week. Rather than starting from scratch, I just tweaked an already existing project, and that already got me to understand lots of the concepts. (Of course, my experience with Angular helped a little as well)&lt;/p&gt;
&lt;h1&gt;
  
  
  Quick links to all code
&lt;/h1&gt;
&lt;h4&gt;
  
  
  App shell
&lt;/h4&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/netflix-piral" rel="noopener noreferrer"&gt;
        netflix-piral
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Netflix clone using microfrontends built as a proof of concept for Piral. This repository contains only the app shell. Built with React. Read more at http://bit.ly/netflix-piral-article
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h4&gt;
  
  
  Pilets
&lt;/h4&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/netflix-browse-pilet" rel="noopener noreferrer"&gt;
        netflix-browse-pilet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The "browse" pilet (microfrontend) for my Netflix clone built with Piral. See also https://git.io/netflix-piral
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/netflix-profile-pilet" rel="noopener noreferrer"&gt;
        netflix-profile-pilet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The "profile" pilet (microfrontend) for my Netflix clone built with Piral. See also https://git.io/netflix-piral
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/netflix-watch-pilet" rel="noopener noreferrer"&gt;
        netflix-watch-pilet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The "watch" pilet (microfrontend) for my Netflix clone built with Piral. See also https://git.io/netflix-piral
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/netflix-favorites-pilet" rel="noopener noreferrer"&gt;
        netflix-favorites-pilet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The "favorites" pilet (microfrontend) for my Netflix clone built with Piral. See also https://git.io/netflix-piral
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DanteDeRuwe" rel="noopener noreferrer"&gt;
        DanteDeRuwe
      &lt;/a&gt; / &lt;a href="https://github.com/DanteDeRuwe/netflix-search-pilet" rel="noopener noreferrer"&gt;
        netflix-search-pilet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The "search" pilet (microfrontend) for my Netflix clone built with Piral. See also https://git.io/netflix-piral
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>piral</category>
      <category>microfrontends</category>
      <category>react</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
