This repo uses Next and TailwindCSS to build a fake vehicle manager web app as an experiment in learning the technologies. It is by no means complete and will be polished over time...!
I have used pnpm
as the package manager so this will need installing first:
npm install -g pnpm
You can then install the project dependencies:
pnpm install
If you prefer to use npm
then delete pnpm-lock.yaml
and run npm install
instead. You can then use npm run <command>
instead of pnpm <command>
.
In order to experiment I have created a small library of components that use
Tailwind for the styling. These can be found in the components
folder. I have
used Storybook to develop all the components which can be started with:
pnpm storybook
I have made a first pass at adding support for dark mode for each component but this is still a work-in-progress. The Storybook config includes a dark mode addon which can be used to toggle between light/dark themes to aid development.
This is my first time using Tailwind and I am quite pleased with the results.
To make managing conditional classes easier I have opted to use the clsx
package (a smaller alternative to the popular classnames
package.)
I found that the Tailwind CSS IntelliSense VS Code extension is an absolute must-have when working with classes.
My only real complaint about Tailwind is the horizontal scrolling required when
using the className
prop and am keen to find ways to make this easier to
manage.
I have not attempted to use any Tailwind extensions and have stuck to the
default font and colour scheme. I would like to spend some time investigating
the official Typography extension and adding semantic colours (e.g. primary
,
secondary
, etc.) to the config rather than using hard-coded colours. This is
the pattern used by Tailwind-based libs such as DaisyUI
for example.
This is also my first foray into using Next.js after years using building SPAs
using create-react-app
and more recently Vite. I am impressed with the
developer experience and have messed with client-side data fetching, SSG and
SSR. For the purproses of this demo the main
branch uses client-side data
fetching via react-query
and some fake data added to Next API routes (in
itself an excellent way to put together a BFF.)
The default out-of-the-box Next.js settings eschews a src
folder in favour of
root-level folders for pages, components, etc. Although I am not crazy about
this pattern I have stuck to it simply because it's the default 😀
I have added additional folders and ended up with the following structure:
Folder | Description |
---|---|
components |
Stand-alone React components |
containers |
Higher-level components that fetch data. Used by pages |
mocks |
Some fake data used by the API and Storybook |
pages |
The Next.js pages, routes and API |
providers |
Good old React Context providers |
queries |
React Query specifics |
types |
Some useful types for the API data |
utils |
Utility functions |
To make these folders easier to access I have configured aliases for TypeScript, Storybook and Vitest. For example:
import { Button } from "@/components";
import { ThemeProvider } from "@/providers";
import { useVehicles } from "@/queries";
import type { Vehicle } from "@/types";
And so on. Turns out this is a common pattern.
The pages themselves are very lightweight and just render things imported from
the containers
folder. I did wrestle with this: I could have done away with
the need for the containers
folder and simply had all that code in each page
but wasn't sure that was an accepted Next.js pattern. Another complication with
lots of code in each page is Storybook: without messing with the default Next.js
page config the stories for this code would need to reside in a different folder
and I felt this was getting messy.
I am interested to hear about better guidance around accepted standards for structuting a Next.js app. Perhaps using atomic design would mean composite components have more structure.
I experimented with getStaticProps
and getServerSideProps
to get a feel for
how these approaches work. Additional repo branches are available that have all
the data fetching shifted to one of these patterns. While SSG in particular
seems like a good fit for ecommerce apps, SSR does have some real benefits. I
would love to see a real-world example of using getServerSideProps
to handle
data fetching as it can reduce the complexity of the client code considerably.
For unit testing I have opted to use Vitest. It is faster than Jest, easier to configure and is actively developed.
On top of vitest
I have added React Testing Library and the jest-dom
matchers.
You can run the tests and start the watcher with:
pnpm test
To run all tests and generate a coverage report:
pnpm test:coverage
There is a small setupTests.ts
file that wires up jest-dom
and a few other
packages.
Note that instead of using jest
-style globals (it
, expect
, etc.) I have
opted to import these in each test if required. This also avoids a potential
TypeScript issue with jest
and vitest
(the @testing-library/jest-dom
package pulls in the jest
types which clashes with vitest
globals.)
I may enhance testing with the addition of some e2e tests using Cypress at some point for completeness.
Also:
- I have added additional
eslint
packages for Storybook and React Testing Library. - I included
storybook-addon-next-router
to make working with Next.js routes in stories easier. - To test the containers I am using Mock Service Worker for the networking. This could also be extended to Storybook if necessary.
- The Create Vehicle form uses
Formik
to manage the form state. - I have used
react-intl
for i18n. I would normally use ababel
plugin to auto-generate message IDs but once this is added Next.js will no longer use the super-fast SWC plugin and falls back tobabel
itself. As this is a demo of Next.js I thought it worth keeping SWC enabled. Support for thereact-intl
plugin is hopefully on the radar. - Some of the components are polymorphic via a MUI-style
component
prop. Getting TS to play nicely here is quite painful. Adding support for refs to these polymorphic components is beyond me at the moment! - I have used Heroicons for a handful of nice SVGs.
- I have kept the components simple and I "use the platform" where possible. This
means I don't have a fancy
Select
component, have not added support for focus trapping to theDialog
, etc. Nor is there much in the way of animations! - I probably need to add more comments!
- To beef up the components I could add MDX docs instead of TSX stories. I have found MDX a much better format to use for publishing component docs using Storybook.
- I found getting dark mode right quite hard so it is not complete.
- More code could be probably be shared.
- Storybook is configured using webpack 5 but the HMR performance is not very good. Any tips to speed it up are welcome (Vite is a potential but it's quite a lot of config and the builder is still a little buggy.)