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
assertLocalefunction from theglobal-components.server.tsfile right before the return statement of theApp()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.tsextension 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.
Task: Enhance the Footer Mock Data
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.