// if url is the variable
export async function getServerSideProps({
req,
res,
params,
}: GetServerSidePropsContext): Promise<ServerSideProps> {
const { url } = params;
return { props: { url } };
}
There are several ways in which we have categorized the files in the folder structure. It is important when placing these files that we think about what area of the application the files support as well as their functional behavior.
Tip
|
Since we’re using TypeScript, instead of .js (plain JS) and .jsx (component) files, we have .ts and .tsx .
.tsx means that the file uses JSX which is a syntax for creating components that looks like HTML.
|
React is built around components.
There can be simple components like Button
that is simply a component version of HTML <button>
.
There can be compound components like a Table
that is made up of multiple smaller components: Row
, Headers
, Cell
, Caption
.
Moreover, every component can have a related test file, utils, and hooks.
Thus, we need a way group all of these related files together.
This grouping we’ll call a "module" (a la ES6 modules). Modules are determined by whether they represent a page, their domain, and their function.
Files representing pages on the site go into pages/
where the directory structure matches the URL that should map to that page.
Thus, the homepage (/
) maps to pages/index.tsx
, the My Account dashboard (/my-account
) maps to /my-account/index.tsx
, and so on.
For dynamic URLs like for PDPs, path variables can be denoted using []
in the file name.
/details/[…url].tsx
would then map any URL that starts with /details
to that component, e.g., /details/green-ghost
.
Path variables can then be accessed in getServerSideProps via the params
prop:
// if url is the variable
export async function getServerSideProps({
req,
res,
params,
}: GetServerSidePropsContext): Promise<ServerSideProps> {
const { url } = params;
return { props: { url } };
}
Important
|
Use
|
There are also a couple of special pages: _app.tsx
and _document.tsx
.
These represent fundamental components that wrap all of the others and are used to initialize the pages.
_app.tsx
_app.tsx
represents the App as a whole and allows the following:
Persisting layout between page changes
Keeping state when navigating pages
Custom error handling using componentDidCatch
Inject additional data into pages
Add global CSS
_document.tsx
_document.tsx
allows customizing the <html>
and <body>
for all pages.
Other pages' contents appear within the <body>
and so it is not accessible from them.
You can add special script tags to the head here, such as for Google Tag Manager, so that they appear on all pages.
<head>
Next provides a Head component that allows a page to update the <head>
such as providing the <title>
and <meta name="description">
tags, which change for each page.
Next provides a Link component to assist in routing transition on the client-side.
This wraps a normal <a>
tag and should be used to navigate between relative URLs on the site.
We’ve creating our own smart <Button>
component for further convenience.
If you pass it a URL, it will render as a link, otherwise a button.
// this
<Button to="/">Go Home</Button>;
// instead of this
<Link href="/">
<a>Go Home</a>
</Link>
Tip
|
We also provide styled versions: PrimaryButton and SecondaryButton .
|
Note
|
We used to instead of href to avoid prop type conflicts with the underlying components.
|
Next.js allows defining API Routes.
We make use of a few such routes under pages/api
.
Most are prefixed with /commerce
as well to keep them distinct from Microservice endpoints when the app is proxied by a gateway.
/health
provides a simple 200 OK response when heat for health checks.
/commerce
/accounts/current
is used to manage the cache that stores the B2B account ID the user has selected to shop within.
Currently, we’re just creating an HTTP-only cookie to store the ID, and we use this to allow server-side requests elsewhere in the app to be able to access the account ID if needed.
/sandboxes/preview
is used to support preview-on-site features with server-side-rendering by parsing the blSandboxPreview
cookie and validating the preview token.
/tenants/resolution
Handles interactions with the Tenant Resolution Endpoint to allow a server-side cache of the result so that page navigation or reloads don’t require re-resolving the tenant information.
The cache is set up in the custom Node server, see custom-server/server.js
Everything but the components directly representing pages go into app/
.
This includes components, contexts, hooks, types, and utils.
These should be organized according to their domain first, then function.
To determine the domain we ask questions like:
Are they simple common components used in many places?
Do they have to do with a PDP only? Only Cart? Browse?
Are there a series of interconnected components, hooks, and utils like for dealing with CSR features?
For example, a component like ProductOptions
that only appears on PDPs would be found under app/product/
.
However, consider a component called Header
that renders the menu navigation, search input, account and cart links.
Because it is part of the general layout of every page, it could go under its own domain of app/layout/
.
Important
|
We prefer to not have domains depend on any others save common, and common should not have external dependencies. |
After determining the domain, the second way we discriminate files is based on their functional classification.
Is this file a React component?
How complex of a component is it?
Is it a utility file or service that is used by components?
There are several functional distinctions that we can make based on the purpose of the file.
Back to the ProductOptions
component, that would further be placed under app/product/components
, but a file called LocaleUtils
would be found under app/common/utils
.
Furthermore, a Header
component can be broken down further such as with a HeaderMenu
subcomponent.
We can, then, have app/layout/components/header/header.tsx
and app/layout/components/header/header-menu.tsx
.
actions/
: objects in the style of flux/https://redux.js.org/basics/actions[redux] actions and action creators
components/
: contains renderable UI elements (see React docs for more info)
contexts/
: custom React contexts
helpers/
: functions like higher-order components and render-props or the more modern custom hooks
hooks/
: custom React hooks
reducers/
: functions in the style of flux/https://redux.js.org/basics/reducers[redux] reducers
services/
: contains classes and objects responsible for performing more complex, often cross-cutting functions that are frequently not directly related to UI elements such as a authentication and site resolution
utils/
: files that contain discrete utility functions for shared logic across components such as null-safe toString()
or forEach()
functions.
Static text should be placed in a messages.ts
file and a react-intl
MessageDescriptor
should be made for each instance.
We can store all of the messages for a domain in a single messages.ts
instead of creating one per component.
Thus, all product components could share one messages file at app/product/messages.ts
rather than scattering the messages around.
Each module should have an index.ts
that exports all of the named components that may be referenced outside of that module.
Thus, app/common/components
should have an index.ts
like the following:
// exports from sub-modules
export * from './form';
// exports from files
export * from './button';
export * from './formatted-amount';
export * from './icons';
export * from './simple-select';
This allows external modules like app/product
to use short import statements when referencing common components
// this
import { Button, InputField } from '@app/common/components';
// instead of this
import { Button } from '@app/common/components/button';
import { InputField } from '@app/common/components/form/input-field';
Important
|
Module indices should have nothing but export statements: No code or logic of their own. |
These are grouped with the component, hook, or util they test and should have the same file name but ending in .test.ts
or .test.tsx
as appropriate.
✔︎ Component files that end in .tsx
✔︎ Non-component files that end in .ts
✔︎ File names that are kebab-case: table.tsx
, table-util.ts
, use-my-hook.ts
✔︎ Files that contain export of same name:
table.tsx
includes exported const Table = () ⇒ {}
use-my-hook.ts
includes exported const useMyHook = () ⇒ {}
✔︎ (Optional) Test files that end in test.ts
or test.tsx
as appropriate for content: Table.test.ts
See Testing
✔︎ messages.ts
file with localization of static text with react-intl
message descriptors for the entire module
See Localization
✔︎ index.ts
that exports named exports for the entire module
✔︎ No code, only exports
app/ |_ auth/ |_ browse-and-search/ |_ cart/ |_ checkout/ |_ cms/ |_ common/ | |_ components/ | | |_ form/ | | | |_ input-field.tsx | | | |_ index.tsx (1) | | | | | |_ button.tsx | | |_ index.tsx (2) | | | |_ contexts/ | |_ hooks/ | |_ utils/ | |_ messages.ts (3) | |_ csr/ |_ layout/ |_ my-account/ |_ product/ pages/ |_ browse/ | |_ [...url].tsx (4) |_ checkout/ | |_ confirmation.tsx | |_ index.tsx (5) | |_ information.tsx (6) | |_ payment.tsx | |_ review.tsx | |_ shipping.tsx | |_ _app.tsx (7) |_ _document.tsx (8) |_ index.tsx (9) |_ cart.tsx (10)
Exports all named exports from the forms
sub-module: export * from './form';
Exports all named exports from the components
module like button
and the form
components
Localized static text for everything in common
Maps all urls starting with /browse/**
Maps to the root checkout page at /checkout
in the browser
Maps to /checkout/information
Custom App component, this wraps all pages and represents the app as a whole
Customizes the <html>
and <body>
tags for all pages
Maps to the homepage: /
Maps to /cart
Besides the main app/
and pages/
modules, you’ll see the following:
.vscode/
: Shared Visual Studio Code settings go here that apply in everyone’s project
coverage/
: Generated testing coverage reports go here
custom-server/
: Contains code for running a custom Node server locally.
This is primarily for when we need to run the
docs/
: Project documentation
messages/
: This contains files with the translations for all of the static text
public/
: Static assets like images and the favicon.ico
results/
: ¯_(ツ)_/¯ something for/from Jest
scripts/
: Utility scripts for building or running the app or related resources
styles/
: Stylesheets go here
test/
: Global test configuration and mocks go here