Sharing code between your backend and frontend 

You might want to share files and functionality between your application backend and frontend. This can help cut down on code duplication and ensure consistency between your server and client logic.

This can be helpful when building with React Router in framework mode or Remix that support server-side rendering, and use action and loader functions to handle data fetching and mutations on the server.

This guide will walk you through shared folder setup.

Setting up a shared folder 

Follow these steps to start sharing files between you api backend and web frontend:

  1. Create a new root level directory for the folder in your project structure. For example /shared.
  2. Add your shared files to this folder. You can organize them into subdirectories as needed. Make sure to export any functions, classes, or constants you want to share. These exports can be imported into your api folder actions and routes using relative filepath imports.

For example, to import function utilFn from shared/index.ts:

api/actions/myGlobalAction.js
JavaScript
import { utilFn } from "../../shared"; export const run: ActionRun = async () => { // call the function const foo = utilFn(); return foo; };
import { utilFn } from "../../shared"; export const run: ActionRun = async () => { // call the function const foo = utilFn(); return foo; };

Or to import utilFn from shared/utils.ts:

api/actions/myGlobalAction.js
JavaScript
// Note the change to the import path import { utilFn } from "../../shared/utils"; export const run: ActionRun = async () => { // call the function const foo = utilFn(); return foo; };
// Note the change to the import path import { utilFn } from "../../shared/utils"; export const run: ActionRun = async () => { // call the function const foo = utilFn(); return foo; };
  1. Update vite.config.mjs so Vite includes the shared folder. Reference your shared folder using the server.fs.allow option:
vite.config.mjs
JavaScript
import { defineConfig } from "vite"; import { gadget } from "gadget-server/vite"; import { reactRouter } from "@react-router/dev/vite"; import path from "path"; export default defineConfig({ plugins: [gadget(), reactRouter()], resolve: { alias: { "@": path.resolve(__dirname, "./web"), }, }, server: { fs: { allow: [path.resolve(__dirname, "./shared")], }, }, });
import { defineConfig } from "vite"; import { gadget } from "gadget-server/vite"; import { reactRouter } from "@react-router/dev/vite"; import path from "path"; export default defineConfig({ plugins: [gadget(), reactRouter()], resolve: { alias: { "@": path.resolve(__dirname, "./web"), }, }, server: { fs: { allow: [path.resolve(__dirname, "./shared")], }, }, });

Gadget uses server.fs.allow under the hood to restrict Vite to the following paths: ["./.gadget/client", "./.gadget/server", "./node_modules", "./public", "./web"]. This is to ensure only the code you want to make public is bundled and served.

Any additions made to server.fs.allow in your Vite config will be merged with these default paths.

  1. After setting server.fs.allow, you can import from your shared folders from inside your project's web folder.

For example, to import function utilFn from shared/index.ts:

web/root.jsx
React
// ... other imports import { utilFn } from "../shared"; // ... the rest of your frontend file
// ... other imports import { utilFn } from "../shared"; // ... the rest of your frontend file

Or to import utilFn from shared/utils.ts:

web/root.jsx
JavaScript
// ... other imports import { utilFn } from "../shared/utils"; // ... the rest of your frontend file
// ... other imports import { utilFn } from "../shared/utils"; // ... the rest of your frontend file

Using resolve.alias for import aliases 

You can also make use of Vite's resolve.alias option to create import aliases for your shared code. This can make your imports cleaner and easier to manage.

For example, to create an alias for a shared folder in vite.config.mjs:

vite.config.mjs
JavaScript
import { defineConfig } from "vite"; import { gadget } from "gadget-server/vite"; import { reactRouter } from "@react-router/dev/vite"; import path from "path"; export default defineConfig({ plugins: [gadget(), reactRouter()], resolve: { alias: { "@": path.resolve(__dirname, "./web"), "@shared": path.resolve(__dirname, "./shared"), }, }, server: { fs: { allow: [path.resolve(__dirname, "./shared")], }, }, });
import { defineConfig } from "vite"; import { gadget } from "gadget-server/vite"; import { reactRouter } from "@react-router/dev/vite"; import path from "path"; export default defineConfig({ plugins: [gadget(), reactRouter()], resolve: { alias: { "@": path.resolve(__dirname, "./web"), "@shared": path.resolve(__dirname, "./shared"), }, }, server: { fs: { allow: [path.resolve(__dirname, "./shared")], }, }, });

And then import using the defined alias, for example, importing utilFn from shared/index.ts:

web/root.jsx
React
// ... other imports import { utilFn } from "@shared"; // ... the rest of your frontend file
// ... other imports import { utilFn } from "@shared"; // ... the rest of your frontend file

Or to import utilFn from shared/utils.ts:

web/root.jsx
React
// ... other imports import { utilFn } from "@shared/utils"; // ... the rest of your frontend file
// ... other imports import { utilFn } from "@shared/utils"; // ... the rest of your frontend file

Was this page helpful?