A fully-functioning template for building modern, maintainable websites.
- Modern design, with automatic dark mode
- Mobile-friendly
- Credentials and restricted pages
- Ability to send emails/texts
- Ability to upload images/files
- Search Engine Optimization (SEO) techniques
Dependency | Purpose | Version |
---|---|---|
ReactJS | UI | ^17.0.2 |
MaterialUI | UI Styling | ^5.0.0-beta.0 |
Apollo | API (GraphQL) | ^2.25.0 |
Prisma | Easier GraphQL building | ^2.30.2 |
ExpressJs | Backend Server | ^4.17.1 |
PostgreSQL | Database | postgres:13 |
Redis | Task Queueing | redis |
Before developing a website from this template, make sure you have the following installed:
- Windows only: Windows Terminal
- Windows only: Ubuntu
- Docker. On Windows, Docker will guide you through enabling Windows Subsystem for Linux (WSL)
- VSCode (also look into enabling Settings Sync)
Note 1: On Windows, NPM and Yarn should be installed from Ubuntu directly.
Note 2: If you want to develop on a remote server (e.g. computer too slow, don't have admin privileges for Docker), you can follow the instructions for setting this up. If this is the case, the only prerequisite you need is VSCode.
VSCode and Docker require additional setup on Windows, since you must use the Windows Subsystem for Linux (WSL). First, make sure the WSL status indicator can appear in VSCode:
- Open VSCode
- Right click the status bar, and make sure "Remote Host" is checked
Next, make sure Docker's WSL integation uses the Ubuntu distro downloaded in step 1:
- Open Docker Desktop
- Go to Resources -> WSL Integration
- Select the Ubuntu distro
- Apply and restart
- Open a terminal in the WSL distro you're using for the project (either from Windows Terminal or the built-in terminal in VSCode)
git config --global core.autocrlf false
git config --global core.eol lf
- To apply this retroactively:
4.1.git rm --cached -rf .
4.2.git diff --cached --name-only -z | xargs -n 50 -0 git add -f
4.3.git ls-files -z | xargs -0 rm
4.4.git checkout .
A more detailed guide (minus step 1) can be found here.
- Follow the deployment steps to learn how to host a VPS and set it up correctly
- Set up a pair of SSH keys. Make note of the file location that the keys are stored in
- In VSCode, download the Remote Development extension
- Enter
CTRL+SHIFT+P
to open the Command Palette - Search and select
Remote-SSH: Open Configuration File...
- Edit configuration file to contain an entry with this format:
Note 1: You can now reference your host by the name you chose, instead of its IP address
Host <any_name_for_remote_server> HostName <your_server_ip_or_hostname> User <your_username> IdentityFile <ssh_keys_location>
Note 2:User
is likelyroot
- Open Command Palette again and select
Remote-SSH: Connect to Host...
- A new VSCode terminal should open. Answer the questions (e.g. server type, server password), and you should be connected!
- Open the
Extensions
page in VSCode, and download the extensions you want to use - After following the environment variables setup, open the Command Palette and select
Ports: Focus on Ports View
- Enter the port numbers of every port in the
.env
file. When running the project, you can now use localhost to access it
In the directory of your choice, enter git clone https://github.com/MattHalloran/ReactGraphQLTemplate
. On Windows, make sure this is done from an Ubuntu terminal in Windows Terminal. If the code is stored on the Windows file system, then docker will be extremely slow - and likely unusable.
To open the project from the command line, enter code <PROJECT_NAME>
from the directory you cloned in, or code .
from the project's directory.
cd ReactGraphQLTemplate
chmod +x ./scripts/* && ./scripts/setup.sh
- Restart code editor
Note: The global dependencies in setup.sh
are global because they're either used by a Dockerfile
or package.json
. If you want to make sure the dependency versions are correct, you should check those files
- Edit environment variables in .env-example
- Rename the file to .env
Edit the file assets/public/business.json
to match your business's data.
By default, the docker containers rely on an external network. This network is used for the server's nginx docker container. During development, there is no need to run an nginx container. Instead, you can enter: docker network create nginx-proxy
Once the docker network is set up, you can start the entire application by entering in the root directory: docker-compose up --build --force-recreate -d
This project is set up so an admin can update various aspects without needing to mess with servers/code. To log in as an admin, use the admin credentials set in the .env
file.
Once you are logged in, you should see a navigation option for "manage site". This includes links and descriptions to all of the admin functions. For inventory upload, there is an file that works with the example database, located in assets/private.
Open Graph is a metadata format that describes how your website should be shown when shared on social media. This data is set in the header, and can be edited at packages/ui/public/index.html
. For more information, here is a guide from Facebook.
- Start:
docker-compose up -d
- Stop:
docker-compose down
- Force stop all containers:
docker kill $(docker ps -q)
- Delete all containers:
docker system prune --all
- Delete all containers and volumes:
docker system prune --all --volumes
- Full deployment test (except for Nginx, as that's handled by a different container):
docker-compose down && docker-compose up --build --force-recreate
- Rebuild with fresh database:
docker-compose down && rm -rf "${PROJECT_DIR}/data/postgres" && docker-compose up --build --force-recreate
- Check logs for a docker container:
docker logs <container-name>
The ESLint VSCode extension is great for catching errors and warnings. It can be configured with an .eslintrc
file.
Database migrations are handled by Prisma. Full documentation can be found here
Database seeding is also handled by Prisma, with full documentation here.
It is generally recommended to store data on an external server, but for smaller projects, local upload/download can also be useful. In this project, admins have a wide array of customization features, such as changing the images in a hero banner. Uploaded data is stored at <project_dir>
/assets
It is often useful to send and receives emails with the website's address. Instructions to set that up can be found here
A favicon is the image displayed in a browser's tab window. It also has other uses, such as being the icon displayed if the site is added to the home screen on a phone. There are a lot of different formats out there, so it's easiest to use a site like realfavicongenerator to guide you through this process. All you need is an svg file of your desired image (usually your company logo).
Favicons should be placed in packages/ui/public
. The link and meta tags should be added to packages/ui/public/index.html
. After your favicon files are in the correct location, if you have a site.manifest
or manifest.json
file, make sure you have the following fields to qualify as a Progressive Web Application (PWA):
name
- Name of application in install dialog and Chrome Web Store. Maximum 45 charactersshort_name
- Short version of application name. Maximum 12 charactersdisplay
- eitherfullscreen
,standalone
,minimal-ui
, orbrowser
scope
- usually/
start_url
- usually/
See here for more information on manifest fields.
If you are planning to support Progressive Web Apps (PWA), then you should also have a square version of your logo referenced in the manifest file. Here is a useful site for checking how your image looks with different masks.
Picking the correct colors for your site can be easy or complicated, depending on how deeply you want to go into it (you could use color theory, for example). You also have to make sure that your theme's colors are different enough from each other to be distinguisable. I use this color tool to create a solid starting point. The site's theme is set in packages/ui/src/utils/theme.ts.
By default, this site automatically sets dark or light theme depending on your browser's settings. This is accomplished in packages/ui/src/App.ts.
The easiest way to use custom fonts is by using Google Fonts. Once a font is selected, you should see some html needed for the font to be loaded. This can be pasted into packages/ui/public/index.html. Then the font can be applied with the font-family CSS tag, as also shown on Google Fonts.
Alternatively, you can supply your own fonts. Using a site such as 1001 Fonts allows you to download a .woff
or .woff2
file for your desired font. This can be placed in packages/ui/src/assets/fonts, and registered in the global css section of packages/ui/src/App.ts like so:
import SakBunderan from './assets/fonts/SakBunderan.woff';
...
"@global": {
...
'@font-face': {
fontFamily: 'SakBunderan',
src: `local('SakBunderan'), url(${SakBunderan}) format('truetype')`,
fontDisplay: 'swap',
}
},
Then, when you need to use the font, you can reference it like this:
navName: {
...
fontSize: '3.5em',
fontFamily: `SakBunderan`,
},
When using a custom font, it is a good idea to compress it using Font Squirrel. If you know which characters you need (such as for a logo), you can also delete unneeded characters via an app like FontForge. In web development, size matters😉
If you want to go even further (though it is probably not necessary), you can also encode your font as a base64 string so it can be used without fetching. On a Unix-based terminal, a .woff
can be converted to base64 using the command base64 -w 0 yourfont.woff > yourfont-64.txt
. Then, you can enter that string into the @font-face
src like so:
...
'@font-face': {
...
src: `local('SakBunderan'), url(data:font/woff;charset=utf-8;base64,insertyourbase64stringhere) format('truetype')`,
...
}
GraphQL is a query language for APIs. It is a faster, understandable, and modernan alternative to REST APIs.
GraphQL syntax errors are notoriously hard to debug, as they often do not give a location. Luckily, this project is structured in a way that allows these issues to be tracked down.
In the schema directory, the GraphQL resolvers are split up into individual files, which are stitched together in the index file. In this file, the models
object is used to combine all of the individual schemas. If you make this an empty array, you can comment out imports until the problem goes away. This allows you to pinpoint which schema file is causing the error. Common errors are empty parentheses (ex: users():
instead of users:
) and empty brackets.
GraphQL is already typed, but it unfortunately doesn't play well with TypeScript's typing system. Instead of creating TypeScript types yourself (which is tedious), they can be generated automatically from an endpoint. This requires the backend server to be running.
- Start project locally in development mode (if ui is not runnable, that's fine. We just need a working server) -
docker-compose up -d
cd packages/server
yarn graphql-generate
- Start project locally in development mode (if ui is not runnable, that's fine. We just need a working server) -
docker-compose up -d
cd packages/ui
yarn graphql-generate
See this video for more details.
Mobile devices can be simulated in Chrome Dev Tools, so testing is usually only done on your main development computer. However, if you'd still like to test on a different device, it will unfortunately not work out-the-box. This is because development uses the localhost
alias. Your device will not be able to resolve this to the correct IP address (ex: 192.168.0.2
), so you have to change it manually. There are 2 places where this must be done: (1) packages/server/src/index.ts; and (2) packages/shared/src/apiConsts.ts.
Lighthouse is an open-source tool for testing any website's (even localhost) performance, accessibility, and Search Engine Optimization (SEO). This can be accessed in Chrome Dev Tools. The tool generates a report in less than a minute, which gives plenty of details and resources that you can look through. This website template is designed to maximize Lighthouse performance by default, but your specific needs may vary. Some places to look at are:
- Compress static images - The easiest way to reduce request payloads is by compressing static images. This can be done on many sites, such as this one for PNGs and this one for SVGs.
- Sitemap.ts and Routes.ts - Automatically generates a sitemap for your website. This helps web crawlers determine which pages are important, and what the pages contain. See this article for more information
- Remove unused dependencies - The easiest way I've found to discover unused dependencies is with depcheck:
- In project's root directory, enter
yarn global add depcheck
depcheck
ornpx depcheck
- Repeat in each package (packages/server, packages/shared, packages/ui)
Before removing packages, please make sure that depcheck was correct. If you are only using the package in a Dockerfile, for example, it may not catch it!
- In project's root directory, enter
- Remove unused components and pages - Every byte counts with web responsiveness! One method for finding unused code is to use ts-unused-exports.
- Peek inside code bundles - Seeing what's inside the code bundles can help you determine what areas of the code should be lazy loaded, and what is taking the most space. To do this:
cd packages/ui
yarn build
yarn analyze
NOTE: When testing for performance, make sure you are running a production build. This can be set with NODE_ENV
in the .env file. If you would like to test performance locally, make sure the SERVER_LOCATION
variable is set to 'local'. Just be mindful that certain performance features (such as cache policy) may be handled by Nginx, so they won't be available locally.
A PWA is a website that can be installed on mobile devices. These don't have quite the same functionality as native apps, but hopefully one day they will. To make your website PWA-compatable, perform an audit on Lighthouse. Then, follow the steps it provides. Make sure NODE_ENV
is set to production
when testing PWA.
One common issue that arises here is "Manifest doesn't have a maskable icon". Clicking on it provides more information, but the gist is that you need a square version of your project's logo. See here for how it should be designed.
A trusted web activity is a PWA that runs natively on Android devices. They can also be listed on the Google Play store, making them almost identical to traditional apps. If this sounds interesting to you, make sure that the packages/ui/public/site.manifest
or packages/ui/public/manifest.json
file has the following data:
- orientation and display to define how the app should feel (likely "any" and "standalone" to feel like a native app)
- screenshots (displayed in the store)
- name, short name, and description
- dir (direction of text) and lang for localization
- icons
- start_url and scope , along with everything else mentioned in the Favicons and PWA sections of this guide. All known manifest fields can be found here.
Once that is complete, you can use the PWABuilder tool to generate other required files and receive further instructions.
When you're ready to test or deploy your app to the Google Play store, visit Google Play Console to receive instructions.
Brave Rewards is a service that allows users of the Brave browser to earn Basic Attention Tokens (BAT) for viewing ads. Enabling this on your website will also allow you to earn BAT. The ads appear as a small popup in the bottom right corner of the browser. To set this up:
- Sign up your website (and your socials if you'd like) here.
- On your website's server (see Deploying Project section to set this up), cd to
packages/ui/build
in your project's directory. vim .well-known/brave-rewards-verification.txt
- Paste the text given in step 1 into the file, and save.
Currently, the cheapest way to deploy a web project seems to be through VPS hosting. Here is an example of how to do this on DigitalOcean. Instead of a plain Ubuntu server, however, it is easier to install one that already contains Docker.
The site can be accessed by the VPS's IP address, but in most cases you'll want to associate the server with a domain name. There are many places to buy domains, but I use Google Domains
Once you buy a domain, you must set up the correct DNS records. This can be done through the site that you bought the domain from, or the site that you bought the VPS from. Here is a good example. Note: DNS changes may take several hours to take effect
The VPS you'll be running this website on must be configured to handle website traffic. This is done through Nginx https://olex.biz/2019/09/hosting-with-docker-nginx-reverse-proxy-letsencrypt/
I've created a project that automates this process.
cd ~
git clone ${PROJECT_URL}
cd ${PROJECT_NAME}
- Edit .env variables
- Make sure that the urls in
packages/ui/public/index.html
point to the correct website chmod +x ./scripts/*
docker-compose up -d