DEV Community

Ibrahim Shamma
Ibrahim Shamma

Posted on

The Nx Node/React Stack part 3 - Next

Let's get straight to it

nx g @nx/next:app site
Enter fullscreen mode Exit fullscreen mode

Here we are creating the next site application, this will be our frontend.

Dialog CSS Library
Choose None we don't want any of these format provided, we will set up tailwind later.

Dialog App Router

Choose app router, if you would like to learn more about it visit the docs

Tailwind Setup

Now we need to set up tailwind, we are also using daisyui component library built upon tailwind

npm install -D tailwindcss
npm i -D daisyui@latest
nx g @nx/react:setup-tailwind --project=site
Enter fullscreen mode Exit fullscreen mode

now we go to tailwind.config.js inside the site app and paste the following boilerplate

import { createGlobPatternsForDependencies } from '@nx/react/tailwind'
import join from 'node:path'

/** @type {import('tailwindcss').Config} */
module.exports = {
    content: [
        join(
            __dirname,
            '{app}/**/*!(*.stories|*.spec).{ts,tsx,html}'
        ),
        ...createGlobPatternsForDependencies(__dirname),
    ],
    theme: {
        extend: {},
    },
    daisyui: {
        themes: [],
    },
    plugins: [require('daisyui')],
}
Enter fullscreen mode Exit fullscreen mode

now go to global.css and replace the existing code with the following

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

App structure

Our structure will be a mix of featured sliced design and redux app structure

Firstly create a src dir inside apps/site then move the app inside the src Next will be able to capture it automatically.

You will end up with folder structure like the following:

apps/site
├── public
├── src
   └── app
Enter fullscreen mode Exit fullscreen mode

Now we will add some other folders to structure our app.

apps/site
├── public
├── src
   └── app
       └── layout.tsx
       └── global.css
       └── page.tsx
       └── [pageName]
           └── layout.tsx
           └── page.tsx
   └── hooks
   └── components
   └── shared
       └── store
       └── helpers
   └── features
       └── [featureName]
            └── api
            └── ui
            └── model
|                └── slice.ts
|                └── types.ts
            └── lib
            └── commands
            └── queries
            └── index.ts
Enter fullscreen mode Exit fullscreen mode

Now let's explain each one responsibility:

app: This is the app directory from Next.js check this Next routing & layouts

shared: This will contain any business logic or fetch library that will be shared across the features.
It will host the redux store and provider,

Side NOTE: make sure to wrap the children of layout.tsx component inside the store provider and add use client directive atop of the module; to be able to use Redux Provider as it lives only on React Client Component.

hooks: This will contain the React hooks, These are the ones that are only related to this monorepo, if you have for example check authorization hook this should be a global hook where you need to create shared next app to store it.

components: This will act like the widget directory in the FSD the idea here you are storing here the pure components or components that access multiple selectors only to render, it must not contain business logic, these the presentational by design.

features: This is heavily inspired by redux features directory but it is a little different.
It combines presentational entities.(e.g., User, Product, Order) and user interactions, actions that bring business value to the user.(e.g. SendComment, AddToCart, UsersSearch). you can do split them they same way described here does. But here we are combining because we are not creating interfaces entirely for the frontend, instead we are sharing them via shared lib.

You can think of it as cross-cutting domain but for UI.

The features contains the following directories

  1. lib: Contains the data manipulation & presentational logic.

    This library and the entire front-end is totally isolated from the business logic, as we in later chapters show where it will live.

  2. commands: You can think of them as the GraphQL Mutations related to the feature, let's say the feature is cart the commands would be for example addToCart, removeFromCart and checkoutCart, all commands start with a commanding verb like create, modify, patch, replace etc.

  3. queries: These hold the presentational responsibilities ui dir. Meaning they will decide what to present or what needed to command the backend. The queries will start with the verb get, assuming we have a feature called product we will have queries like getProductByName, getRecommendedProducts.

  4. ui: It is the feature's React components basically they are the components that depend upon the feature store where it will render some jsx, there are some unwritten rules here we need to address them, think of them as a simple but with store accessibility and an onClick action.

  • Components Should never take props or depend on other features' slices this violates SRP
  • Components will present final results cooked in the slice, meaning it will not depend useState or the lib. The decision of what it will present was already made and stored inside the slice.
  1. model: this dir will contain two modules
  2. slice.ts: where we will create the redux slice
  3. types.ts: this directory contains the extended types for the ui from the shared types

  4. api: This directory will contain the async redux actions, this directory will depend upon the lib,commands,queries heavily.
    Let's say we are working on an Markdown editor and we are creating an API that gets the file from the server look at the following:

  5. You will need to take the fetch lib or executeQuery from the shared dir

  6. The query statement and params will be managed in queries modules.

  7. lib will manage data post processing before the api 'stores' the result into the slice

  8. The ui will be accessing the respected slice and manages presenting the UI.

There are general rules to be followed here

  • All functions are camelCase, components are PascalCase and all dir are kebab-case

  • outside the feature directory nothing can be imported directly, we are only exporting the ui components and the store, in this regard we are applying the following eslint rule in the coming parts of this series.

    Note each feature has one barrel file, if you put barrel file in ui folder and import it in the feature one, it may cause issues with bundling

  • All feature ui components are react client components meaning each module will start with use client, because simply we are utilizing the user slice aka user state.
    So Try to but in the feature component as much less UI as possible and leave the main components to take care the rest while utilizing RSC

What else you would do to improve our template?

We as Engineers love to ask ourselves what else we can improve.

Here are some tips but not limited to:

  1. Separate related components into a an independent lib this way need to import them dynamically and this can improve FCP

  2. If the features are growing and changing, more teams are created with this rule, you need to look at various Micro-Frontend options, look at Module Federation where you can split your app into mirco-apps each has its own responsibilities and deployed independently, the special thing with module federation is that modules are imported in runtime.

  3. Create an UI lib where will host all UI components needed for the entire app, this way you can share the same theme and colors across the app.

In summary, the provided guide not only aids in building a solid foundation for a frontend application but also emphasizes the importance of architectural decisions that can enhance collaboration, development speed, and the overall user experience. By embracing the recommended practices and considering future enhancements, developers can create a robust and adaptable frontend solution.

Top comments (0)