Skip to content

Global Components

This section focuses less on implementation and more on exploring some core Remix features and the mocking capabilities of the stack, using Global Components as a hands-on example.

What Are Global Components?

The Global Components package unifies platform-wide elements such as navigation, footers, and contact modules. These components are served dynamically with data from a dedicated backend. For more details, head over to the Global Components repository.

To summarize

  • Unified components: Provides reusable components like navigation, footer, and contact modules.
  • Backend integration: A backend serves the components with the necessary data.

How to Use Global Components

Most Global Components packages are pre-installed and already integrated into your project. You can see examples of their usage in the root.tsx file by searching for GlobalNavigation, FooterSmall, or ContactsModule. Since root.tsx is the main layout file, these components are available across all pages of your application.

Loader and Server Files

One key Remix feature in action here is the loader function. Loaders for example can fetch and provide data to components rendered in the App() function of the root.tsx file. To learn more, check out the official Remix documentation on loaders.

A key point to note:

  • Server-side execution only: The loader function runs on the server and is not included in the client-side JavaScript bundle.
  • Every page can define a "loader" function.

Note: If code need to be kept secret, you need to use a server file, otherwise code will be part of the source-maps. See Remix documentation for more information: Server Modules

Additionally, Remix allows you to create "server-only" files by naming them with .server.ts (e.g., global-navigation.server.ts in /services). These files ensure certain functions are only executed on the server. This approach prevents server-only code from being exposed to the client.

Important: If you accidentally use a "server-only" function in client-side code (e.g., within App()), it will result in an error.

πŸ“ Task: Test Server-Only Code in the Client

To see how Remix enforces server-only restrictions, try the following:

  • Open the root.tsx file.
  • Add the server-only assertLocale function from the global-components.server.ts file right before the return statement of the App() function.
const location = useLocation();

console.log(assertLocale(locale));

return (
  <html lang={locale} dir={i18n.dir()} data-base-path={basePath}>
    ...
  </html>
);

And now reload the page http://localhost:3000/remix-workshop/welcome.

After reloading the page, you won’t see any immediate changes in the browser. However, your terminal will display an error like this:

15:19:23 [vite] Internal server error: Server-only module referenced by client

Another case is an error during build. Stop the development server (Cmd + C), and run pnpm run build. The build will fail, showing a similar error, but in a more detailed format. This failure ensures such mistakes don’t go unnoticed before deployment.

Why This Matters

Accidentally exposing server-only code on the client can lead to critical issues, including potential security vulnerabilities. For example:

  • Sensitive data exposure: Using API keys or sensitive server-side logic in client-side code can make them publicly accessible.
  • Broken builds: Using server-only code on the client will prevent successful deployment.

Using "server-only" files is a reliable way to separate server logic from the client and safeguard your application.

Global Components in Practice

In the case of Global Components, the loader function fetches data from the Global Components backend and passes it to the component. By using the useLoaderData hook, this data becomes accessible within your component.

Simplified example from the root.tsx:

// Server Part
export async function loader({ request }: LoaderFunctionArgs) {
  // fetch data
  const [desktopNavigationData] = await Promise.all([
    getNavigationData(locale, "desktop", request),
  ]);

  // return data to the client part
  return json({ desktopNavigationData });
}

// Client Part
export default function App() {
  // take the server data
  const { desktopNavigationData } = useLoaderData<typeof loader>();

  // render component with data from server
  return <GlobalNavigation data={desktopNavigationData} />;
}

This seamless integration of server-side data fetching and client-side rendering is one of the key strengths of Remix.

Quick Deep-Dive into Mocking and Why It Matters

The 4.0 Remix Stack follows the idea of mocking every API request for the development environment and Lambda pull request deployments. Mocking means that API calls are intercepted, and a predefined set of data is returned as the response. This is achieved using the Mock Service Worker (MSW), which intercepts outgoing requests and provides mock responses.

This approach is vital because it ensures that the application remains independent of external APIs or specific API configurations. It improves developer experience (DX) and creates a stable, reliable test environment. Local development, CI integration tests, and Lambda deployments per PR run exclusively with mock data, allowing developers to craft test cases that cover all scenarios and maintain a robust, predictable application.

Here's an example of a mocked API response:

export function registerDefaultMocks(server: typeof MockServer) {
  server.use(
    http.get("/api/welcome/", async () => {
      return HttpResponse.json({
        message: "Welcome to the Remix Workshop, Max!",
      });
    }),
  );
}

Key Details About Mock Files

  • Mock files must have the .mocks.ts extension for their functionality to be recognized.
    • The development server will search for this files and will set up mocking with them.
  • In the example above, any API call to /api/welcome/ is intercepted and responds with JSON data.
  • The returned JSON can include any data, simulate errors, or introduce response delays. The possibilities are flexible and defined by your test cases.

For more advanced features, check out the MSW documentation.

Global Components Mocks in Practice

Using Global Components as a practical example, you'll find a file named global-components.mocks.ts in the project. Since you now know this file likely contains mock data, take some time to explore its contents and see how it works.

To understand the potential of this approach, let's enhance the footer mock data. For example, add an item labeled Remix Workshop that links to /remix-workshop/welcome.

The getFallbackFooterData function already returns an array of items, where each item includes a label and a url (both of type string). For reference take a look into the Global Components repository: https://github.com/PIT-Marketing-Content/global-components/blob/main/packages/footer/src/fallback-footer-data.ts#L50-L84. But, please be aware, the API will return only the items for the respective language.

Try enhacing the already provided mock-data for the footer by adding another entry, then compare your solution to the one below.

Note: After making changes to a .mock.ts file, restart the development server for the changes to take effect. Once done, reload the page to see the new navigation item in the footer.

Solution

You can use array destructuring to enhance the mock data like this:

const data = [
  ...getFallbackFooterData(size, locale),
  { label: "Remix Workshop", url: "/remix-workshop" },
];

Here’s how it integrates with the server mock:

server.use(
  http.get(
    /https?:\/\/www\.statista\.com\/global-components\/footer-data/,
    async ({ request }) => {
      const url = new URL(request.url);
      const size = url.searchParams.get("size") ?? "small";
      const locale = url.searchParams.get("locale") ?? "en";

      const data = [
        ...getFallbackFooterData(size, locale),
        { label: "Remix Workshop", url: "/remix-workshop" },
      ];

      return HttpResponse.json(data);
    },
  ),
);

This method lets you tweak and adapt API responses as needed, empowering you to create flexible test scenarios. It ensures that development and testing are controlled and independent from any "real world" API to create a great developer DX and stable test environment.

Congratulations! πŸŽ‰ You've successfully learned about some core features of Remix and how the stack handles APIs and mocking.