Frontend


Overview

The frontend is in React + InertiaJS. Support Vue + Svelte is coming soon. Inertia makes it unbelievably easy to create slick, fast Django apps. It is highly flexible as well, single-page apps.

Here is the blog post about getting started with InertiaJS + Django. It gives a full overview for getting started with InertiaJS + Django.

Folder Structure

text
- 📁 assets
  - 📁 css
  - 📁 js
    - 📁 components
    - 📁 hooks
      - 📁 api
      - 📄 use-api-client-hook.jsx
      - 📄 use-at-bottom.jsx
      - 📄 use-copy-to-clipboard.jsx
      - 📄 use-enter-submit.jsx
      - 📄 use-eventstream.jsx
      - 📄 use-local-storage.tsx
      - 📄 use-pydantic-error-parser.jsx
    - 📁 layouts
    - 📁 lib
    - 📁 pages
      - 📁 app
      - 📁 auth
      - 📁 cp
      - 📁 marketing
      - 📄 error.jsx
      - 📄 app.jsx
      - 📄 error.jsx
    - 📄 ssr.js
  • css - the app styles are defined here. This is where you update the theme for the app.

  • components - all the ShadCN components are defined in here.

  • hooks - react query mutations and queries are defined in the api folder. More on that later. There are other experimental utilities here that you will find useful, for example use-eventstream.jsx for streaming.

  • layouts - your app layouts, there are different layouts for different sections of the app.

  • lib - includes all utility functions

  • pages - defines all the components for the app.

    • auth - everything in the accounts app

    • app - main app including user settings

    • cp - control panel components

    • marketing - frontend pages

Working with Inertia + Framework

I highly recommend starting with the blog post if you are not familiar with InertiaJS. This assumes you now have some working knowledge InertiaJS + React.

The entry point

javascript
import 'vite/modulepreload-polyfill'
import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
import '../css/app.css'
import axios from 'axios'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import DefaultLayout from '$/layouts/top-nav.jsx'
import SettingsLayout from '$/layouts/settings.jsx'
import BillingLayout from '$/layouts/billing.jsx'
import { tracking } from '$/lib/sentry'

axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.defaults.xsrfCookieName = 'csrftoken'
const queryClient = new QueryClient()

// sentry
tracking()

createInertiaApp({
  resolve: (name) => {
    const pages = import.meta.glob('./pages/**/*.{jsx,tsx}', { eager: true })
    const page = pages[`./pages/${name}.jsx`] || pages[`./pages/${name}.tsx`]
    let Layout
    if (!name.startsWith('marketing/') && !name.startsWith('auth/') && !name.startsWith('cp/') && !name.startsWith('error'))
      Layout = DefaultLayout
    if (name.startsWith('app/settings/'))
      Layout = SettingsLayout
    if (name.startsWith('app/billing/'))
      Layout = BillingLayout
    if (!Layout)
      return page

    page.default.layout = page.default.layout || (page => <Layout children={page} />)

    return page
  },
  setup({ el, App, props }) {
    createRoot(el).render(
        <QueryClientProvider client={queryClient}>
          <App {...props} />
        </QueryClientProvider>,
    )
  },
})

This is the full entry point file here are the main points:

  • The app.jsx file is where all the components are imported

  • The tracing function sets up tracking and error monitoring using a tool like Sentry.

  • We import different layout components for different sections of the app.

  • CSRF token name is configured for axios requests

  • A QueryClient instance from @tanstack/react-query is created for data fetching and caching.

Vite for local development and asset bundling

We use Vite to serve the frontend assets during development and building assets for production. We use pnpm to manage packages.

Config file

text
import path from 'node:path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import Icons from 'unplugin-icons/vite'

const PRODUCTION_BASE_PATH = '/static/dist/'
const DEVELOPMENT_BASE_PATH = '/static/'

/**
 * Returns the base path for the current mode.
 * **/
function buildBasePath(mode) {
  return mode === 'production' ? PRODUCTION_BASE_PATH : DEVELOPMENT_BASE_PATH
}

/**
 * Experimental: renderBuiltUrl
 * Return empty object when not using CDN.
 */
function experimentalSettings() {
  return {
    renderBuiltUrl(filename) {
      return `https://base-cdn.advantch.com/static/dist/${filename}`
    },
  }
}

export default defineConfig(({ mode }) => {
  return {
    plugins: [react(), Icons({ autoInstall: true })],
    root: '.',
    base: buildBasePath(mode),
    server: {
      host: 'localhost',
      port: 5173,
      open: false,
      watch: {
        usePolling: true,
        disableGlobbing: false,
      },
    },
    resolve: {
      extensions: ['.js', '.json', '.jsx', '.ts', '.tsx', '.css'],
      alias: {
        $: `${path.resolve(__dirname, 'assets/js/')}`,
      },
    },
    build: {
      assetsDir: '',
      manifest: true,
      emptyOutDir: true,
      target: 'es2018',
      rollupOptions: {
        input: {
          main: './assets/js/app.jsx',
        },
      },
      outDir: './static/dist',
    },
    experimental: experimentalSettings(),
  }
})

Everything here should be already familiar. A couple of things to point out.

  • experimental key includes the renderBuiltUrl. This will be used for appending the correct base path to assets if you are deploying to a CDN. Get in touch if you facing any issues. See the docs for more customisation .

  • we use unplugin-icons to enable auto importing of icons during development.

  • See the resolve key for the file types that will be resolved and the alias used in imports.

Here is a snippet of the package.json file for the main scripts.

json

 {
   "scripts": {
    "dev": "vite --mode development",
    "build:wn": "vite --mode whitenoise",
    "build": "NODE_ENV=PROD vite build --mode production",
    "preview": "vite preview",
    "lint": "eslint . --fix"
  }
}
  • dev - this is used by the cli when in dev mode. Starts the vite dev server.

  • lint - use this to run lint. We use antfu eslint config.

Running the dev server

I recommend using the vanty cli for building and running the local stack.

text
$ vanty dev init 
$ vanty dev start
>> 
....
09:56:47 vite dev     | 
09:56:47 vite dev     | > the-starter-kit@0.16.1 dev 
09:56:47 vite dev     | > vite --mode development
09:56:47 vite dev     | 
  • Run vanty dev init and vanty dev start to run the stack. This will also bring up the server.

Vue + Svelte (coming soon)

Building for production

Use pnpm run build to build for production. You can build your assets manually during development or update your pipeline to include the correct build commands. Adjust your deploy pipeline to include the following

text
      # nodejs and pnpm
 - name: Setup Node.js
   uses: actions/setup-node@v3
   with:
     node-version: '18'

 - name: Install versions
   run: |
     npm install -g pnpm
     pnpm run build

 - name: Build staticfiles
   uses: stefanzweifel/git-auto-commit-action@v4

 - name: Collect files
   run: DJANGO_SETTINGS_MODULE=config.settings.pipeline IS_GITHUB_WORKFLOW='true' python manage.py collectstatic --noinput