React and Next.js
The Catalyst storefront is, at the simplest level, an opinionated Next.js application, and Next.js is a full-stack web framework built on React. Understanding the basic architecture of React and Next.js, therefore, goes a long way toward understanding Catalyst as a whole.
While a full review of the details of these technologies is beyond the scope of this course, it’s helpful to examine a few of their key architectural elements.
Note that Catalyst uses the Next.js App Router architecture, which differs in significant ways from the legacy Pages Router. Some details we’ll review here are unique to the App Router.
React
React is a Javascript-powered library for rapidly and easily building user interfaces, and React components are central to the visual design system in Catalyst.
Compared with a templating language like Handlebars in Stencil, React components offer similar capabilities for expressing dynamic data but also encompass much more. React interfaces are a tree of modular components, which are actually JavaScript functions encapsulating both the logic and presentational markup relevant to a specific piece of UI.
React is a large topic. If you’re new to the technology, see the official guide to start. The following are some of the basic concepts.
Functions and JSX
React components are functions that return JSX code - an HTML-like markup that allows mixing in JavaScript expressions.
JavaScript Expressions
Wherever braces appear within JSX, the code within them is a JavaScript expression. This is essential for working with dynamic data.
Referencing dynamic values, conditional output, loops, and similar constructs require no specialized syntax. Familiar JavaScript syntax like object notation, comparison operators, and array functions are used for these tasks.
State
State values provide the “memory” of a React component, tracking data that changes in response to user interaction or external systems.
State is key to creating highly dynamic UI, making it easy to incorporate even very complex combinations of states by simply describing conditional output with JSX.
React Example
The following example demonstrates a straightforward implementation of the concepts we’ve described.
React Server Components
React is a traditionally front-end language, handling the work of rendering a UI in the user’s browser. While frameworks like Next.js added server-side rendering of React components for full page loads, the traditional Pages Router architecture still has a rigid separation between “server only” logic like server-side data fetching and the components responsible for rendering. This hard line between back-end logic and rendering is fairly typical of traditional web frameworks.
The App Router enables the use of React Server Components. The logic within Server Components is guaranteed to be executed on the server side. The JSX within a component (that is, the React presentation code) might be fully rendered into HTML on the server or might be executed on the client side along with a payload from the server, but all other component code is never exposed in the browser.
This architecture means that server-side logic like data fetching can be incorporated directly into a React component. Let’s look at an example from the route corresponding with the main cart page, app/[locale]/(default)/cart/page.tsx:
The API client is used to make a server-side request to the BigCommerce GraphQL Storefront API for cart data. In the legacy approach, this fetch would be done in a separate server-side function and the result passed into the component as a prop. With Server Components, it is performed directly within the component.
The advantage of this is that server-side logic can be included in any component concerned with that logic, regardless of where that component occurs and in what pages, rather than being centralized in a single entry point.
Client Components
Standing in contrast with Server Components are Client Components, which operate more like React components always have. The following expression at the top of a component file explicitly identifies it as a Client Component:
Client components are used at the points in a rendered page where interactivity or browser APIs are needed.
Server Actions
Just as Server Components allow the back-end logic related to rendering to be tightly coupled with the components performing that rendering, Server Actions do the same for back-end logic initiated by user interactions.
To use another example from the Catalyst core, take a look at app/[locale]/(default)/webpages/contact/page.tsx.
DynamicForm is a Client Component. The callback function passed to the action property is called as an event handler function when the form is submitted. This handler executes within the browser.
Examine the file where this is function is defined (app/[locale]/(default)/webpages/contact/_actions/submit-contact-form.ts), and you’ll see this expression at the top:
This defines the function as a Server Action and fundamentally changes the way this interaction occurs. While the code simply appears to represent a function call, in the browser Next.js will actually initiate a request to the server, with the function input as a payload, and the Server Action’s return value will then be sent back to the browser in the response.
The more legacy approach would likely involve writing a separate API endpoint for the user interaction to make a request to. Like Server Components, Server Actions make it easier to couple back-end logic with related components.
Server Components and Server Actions are extensively used throughout the Catalyst storefront and a foundational part of its architecture.