Return to Blog Home

Just Writing React

Source 

Just Writing React

When you just want to write React without worrying about the rest!

React is a great library for building user interfaces, but React is not alone - it's build ontop of the sholders of giant libraries and build systems. This is great for the community and advanted users, but it can be a bit overwhelming for new developers, who are learning to just write React.

Hence this post - where I explain a few strategies for just writing React, without worrying about the rest, from simple frontend-only projects to more complex projects with a backend.

Babel Standalone

Ones first frontend app usually is just a single HTML, CSS and JS file - so I think ones first React frontend should be the same - so I'll start with Babel Standalone.

The advantage of Babel Standalone is that there is no build system, it is - as it's name suggests - a standalone script that you can include in your HTML file, and (with a few customization and limitations) it will transpile your modern JSX-riddled Reactcode on the fly.

The main disadvantage of Babel Standalone is actually it's pro - being without any build system not only means that there is a few seconds of a blank screen as all this code is transpiled on the client, but also that you can't easily use any other libraries, or get any modern development features such as hot reloading.


My entire Babel Standalone script is available as a Gist.

It starts by adding this to your HTML <body>:

<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<!-- import React from 'react' -->
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- import ReactDOM from 'react' -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script>
    Babel.registerPreset('rascal-two@multifile-babel-standalone-react', {
        presets: [ [Babel.availablePresets['react']] ], // Handle <abbr title="JavaScript"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript">JS</a></abbr>X
        plugins: [ [Babel.availablePlugins['transform-modules-umd']] ], // Allow ESM importing
        moduleId: 'main'
    });
</script>
<!-- scripts must be in dependant order, aka App.js import <a href="https://developer.android.com/reference/android/widget/Button">Button</a>.js, <a href="https://developer.android.com/reference/android/widget/Button">Button</a>.js must appear before App.js -->
<script type="text/babel" data-plugins="transform-modules-umd" src="./<a href="https://developer.android.com/reference/android/widget/Button">Button</a>.jsx"></script>
<script type="text/babel" data-plugins="transform-modules-umd" src="./App.jsx"></script>
<script type="text/babel" data-presets="rascal-two@multifile-babel-standalone-react">
    import App from './App'
    ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>

Now while this is a bit of code and it may be verbose, it is also relatively readable - first two scripts are the equlivient of importing React and ReactDOM, the third is creating a preset that will tell Babel how to handle both JSX and ESM imports, with the remaining scripts being your code - in the order it's needed, and finally the last is your index.js but inlined.

Aside from this, the rest of the is nearly identical to code you would write normally with one one caveat - you can't import anything from React or ReactDOM - you have to use them as global variables:

// instead of
import React from 'react'
// React is already imported everything

// and instead of
import { useState } from 'react';
// you have to destructure it from the global
const { useState } = React;

The usage of Babel Standalone for frontend-only and fullstack projects is identical, as there is no build system - the only change you can make if you wish is to make all the scripts served locally, instead of from a CDN.

Vite

Vite is nice modern way to develop React frontends, not only do you get most of the modern development features such as hot reloading, but you can additionally customize it your hearts content without breaking anything.

Now the Vite docs themselves are quite extensive in most things, so I'm only going to cover the process for things that aren't covered in the docs one may wish to do.

After writing your amazing frontend React code, when you want to actually use it with a backend there are a few steps one must take, no matter what deployement method you use.

The first is allowing the frontend to access the backend via the development server, which in Vite is done by adding a proxy option to the vite.config.js file:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:2121/',
        changeOrigin: true,
      },
    }
  }
})

This allows all requests to /api to be proxied to the backend server instead of the frontend development server.

Create-React-App

While Create-React-App has fallen out of favor with the emergence of more resourve-friendly alternatives such as Vite, it still works and many existing projects are using it.

Allowing the frontend to access the backend is quite simple, all one needs to do is add the "proxy" property to the package.json:

    "proxy": "http://localhost:2121",

which will send all requests that fail to the backend server, which was running on port 2121 in this example.

Vite & Create-React-App

Now both of these - and technically any frontend that requires building - need a few additional steps to get working.

These do depend on your deployment strategy, starting with my favourite is deploying the frontend within the backend, as a single server.

Single Fullstack Deployment

With this strategy, the frontend is built and served by the backend server, and the backend server is the only server that needs to be running.


The first step is moving your frontend Vite project into the backend project directory, and I do so by calling it frontend, then linking it as a dependency by adding some npm run scripts:

    "scripts": {
        "start": "node server.js",
        "dev": "concurrently 'nodemon server.js' 'npm run start --prefix frontend'",
        "build": "npm run build --prefix frontend",
        "postinstall": "npm install --prefix frontend"
    }

First is the postinstall, which is ran right after the npm install command is ran, and triggers the installation of the frontend dependencies.

postinstall isn't a special script name, it's just a script name prefixed with the post keyword, and this pre/post suffixing works for any script name.

Next is the build script, most platforms that support Node.js will automatically run this for you, and in this case is runs the build script within the frontend directory, which builds our React code into static assets within the frontend/dist folder.

Lastly - and optionally - is the dev script, which is a script that runs both the backend and frontend servers at the same time, using the concurrently package, which is installed as a dev dependency, allowing one to not have to spin up two different terminals to run both servers.


Lastly there are two changes that are needed to be made to the backend server to allow it to serve the frontend code, the first is to add/modify the express.static middleware to serve the frontend code:

app.use(express.static('frontend/dist'));

Two Separate Deployment

With this strategy, the frontend is deployed separately from the backend, and the backend is only responsible for serving as an API for the frontend.


The first step is the easiest, which is adding the cors() middleware to the backend server, allowing requests from the frontend to be made:

app.use(cors());

You can customize this further to allow only requests from where the frontend is deployed to, but that is out of scope for this guide.

Next - if you have any form of routing - you need to add a catch-all route to the backend server, which will serve the frontend code, and this is done by adding the following to the bottom of the server.js file:

app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, 'frontend/dist/index.html'));
});

This needs to be the last handler in the server, as it will catch all requests that don't match any other routes.


The last step is to update all your interactions with the backend from the frontend code to be absolute URLs, instead of relative URLs, as the frontend will be deployed to a different domain than the backend.

You can do this however you want, but at bare minimum I usually add the URL of the backend as a environment variable, and then use that in my code, allowing me to easily change it if I need to.

Additionally if you're using authentication, you'll need to ensure your network requests are saving & sending cookies - which is done by adding the credentials: 'include' option to the fetch request, or the withCredentials: true option to the axios request.

Conclusion

While this is far from extensive - I'm a big fan of Next.js for example and other great frameworks, but those are not vanilla React, learning those first commonly causes one to mixup what is React and what is insert chosen framework.

Hopefully this has helped you just write some React and not have to worry too much about what to build your React ontop of!