Skip to content

Instantly share code, notes, and snippets.

@ManotLuijiu
Last active March 29, 2025 12:19
Show Gist options
  • Select an option

  • Save ManotLuijiu/1f5aa8189c17dbba458d91e94f5f5e98 to your computer and use it in GitHub Desktop.

Select an option

Save ManotLuijiu/1f5aa8189c17dbba458d91e94f5f5e98 to your computer and use it in GitHub Desktop.
Frappe/ERPNext custom app files structure at beginning point

Understanding Jinja vs. Vite.js (React.js) Build Processes in ERPNext

When developing custom applications in ERPNext, managing frontend assets efficiently is crucial. Traditionally, ERPNext uses Jinja templates to build web pages, while modern frontend frameworks like Vite.js with React.js offer a more dynamic approach. In this blog post, I'll explore how Jinja and Vite.js handle builds, where to place the build files, and how to integrate them into an ERPNext custom app.


How Jinja Builds and Where to Place Build Files

Jinja in ERPNext

Jinja is a Python-based templating engine used in ERPNext for rendering HTML pages with dynamic data. It is tightly integrated into Frappe’s framework and is responsible for rendering both Desk and Web pages.

How Jinja Processes and Renders Files

  1. Template Location: Jinja templates are usually stored in:

    • your_app/your_app/templates/pages/
    • your_app/your_app/templates/includes/
  2. Rendering Process:

    • When a page is requested, Frappe fetches the corresponding Jinja template.
    • The template is processed on the server-side, injecting data from Python controllers (your_app/your_app/www/).
    • The final HTML is sent to the client.
  3. Static Assets (CSS, JS, Images):

    • Placed inside your_app/public/
    • Served via /assets/your_app/ URL
  4. Build Output Location:

    • Jinja itself doesn’t have a “build” process like modern frontend frameworks. Instead, it relies on server-side rendering with static files placed in public/. However, ERPNext does generate bundled assets (JS, CSS) during the bench build process, which are stored in the public/dist/ folder.

Example Folder Structure for Jinja

your_app/
├── public/
│   ├── js/
│   │   ├── custom.js
│   ├── css/
│   │   ├── style.css
│   ├── images/
│   │   ├── logo.png
│
├── templates/
│   ├── pages/
│   │   ├── dashboard.html
│   ├── includes/
│   │   ├── navbar.html

Pros & Cons of Jinja Rendering

✅ Server-side rendering makes it SEO-friendly.
✅ Integrated directly into ERPNext.
❌ Less interactive and requires full page reloads.
❌ Harder to develop modern, component-based UIs.


How Vite.js (React.js) Builds and Where to Place Build Files

What is Vite.js?

Vite.js is a modern frontend tooling system that significantly improves performance by enabling faster builds and hot module replacement. It is commonly used for React.js applications in ERPNext.

How Vite.js Builds Assets

  1. Source Files:

    • React.js components are written in src/.
    • CSS, JS, and images are managed inside src/assets/.
  2. Development Mode:

    • Uses a local development server with hot reloading.
    • No need to manually refresh the page.
  3. Production Build:

    • Running vite build generates optimized assets in the dist/ folder (under Doppio configuration).
    • These assets must be served inside ERPNext’s public/ directory.
  4. Where to Place Build Files?

    • Move dist/ files to your_app/public/frontend/.
    • Serve JavaScript inside Desk Pages using:
      frappe.pages['custom-page'].on_page_load = function(wrapper) {
          let $page = $(wrapper);
          $page.html('<div id="react-app"></div>');
      
          let script = document.createElement("script");
          script.src = "/assets/your_app/frontend/assets/index.js";
          script.type = "module";
          document.body.appendChild(script);
      };
    • Doppio take care all of above

Example Folder Structure for Vite.js (React.js) with Doppio

your_app/
├── frontend/
│   ├── src/
│   │   ├── App.jsx
│   │   ├── components/
│   ├── assets/
│   ├── index.html
│
├── public/
│   ├── frontend/
│   │   ├── assets/
│   │   │   ├── index.js
│   │   │   ├── styles.css

Pros & Cons of Vite.js (React.js) with Doppio

✅ Faster frontend performance with optimized builds.
✅ Component-based UI for modular development.
✅ Uses Doppio for easy integration into ERPNext.
❌ Requires a build step (doppio build).
❌ Extra setup needed to integrate with ERPNext’s Desk Pages.


Jinja vs. Vite.js: A Comparison

Feature Jinja (ERPNext) Vite.js (React.js) with Doppio
Rendering Server-side Client-side (SPA)
Interactivity Low High (dynamic UI)
SEO Good Requires SSR
Build Process None (templating) Needs doppio build
Where to Place Assets public/ public/frontend/
Usage Simple pages, static content Interactive dashboards, SPAs

Integrating Vite.js with Doppio into ERPNext (Best Practices)

1️⃣ Install Doppio in Your Development Bench

cd your_bench
bench get-app https://github.com/NagariaHussain/doppio

2️⃣ Build the Frontend

bench add-spa

Conclusion

By leveraging Vite.js with Doppio, you can build modern React.js interfaces inside ERPNext, improving user experience while maintaining flexibility. This setup allows for faster development, component-based UIs, and better performance compared to Jinja templates!

With Doppio, you get a streamlined way to integrate Vite.js into ERPNext, making your frontend workflows more efficient and scalable.

❯ cd apps/raven_assistant/frontend
❯ git status
On branch develop
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: ../README.md
modified: ../raven_assistant/hooks.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
./
../package.json
no changes added to commit (use "git add" and/or "git commit -a")
❯ git remote -v
❯ yarn add tailwindcss @tailwindcss/vite
yarn add v1.22.22
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 13 new dependencies.
info Direct dependencies
└─ @tailwindcss/vite@4.0.17
info All dependencies
├─ @tailwindcss/node@4.0.17
├─ @tailwindcss/oxide-linux-x64-gnu@4.0.17
├─ @tailwindcss/oxide-linux-x64-musl@4.0.17
├─ @tailwindcss/oxide@4.0.17
├─ @tailwindcss/vite@4.0.17
├─ detect-libc@2.0.3
├─ enhanced-resolve@5.18.1
├─ graceful-fs@4.2.11
├─ jiti@2.4.2
├─ lightningcss-linux-x64-gnu@1.29.2
├─ lightningcss-linux-x64-musl@1.29.2
├─ lightningcss@1.29.2
└─ tapable@2.2.1
Done in 3.66s.
📦raven_assistant
┣ 📂frontend
┃ ┣ 📂public
┃ ┃ ┗ 📜vite.svg
┃ ┣ 📂src
┃ ┃ ┣ 📂assets
┃ ┃ ┃ ┗ 📜react.svg
┃ ┃ ┣ 📜App.css
┃ ┃ ┣ 📜App.tsx
┃ ┃ ┣ 📜index.css
┃ ┃ ┣ 📜main.tsx
┃ ┃ ┗ 📜vite-env.d.ts
┃ ┣ 📜.gitignore
┃ ┣ 📜README.md
┃ ┣ 📜eslint.config.js
┃ ┣ 📜index.html
┃ ┣ 📜package.json
┃ ┣ 📜proxyOptions.ts
┃ ┣ 📜tsconfig.app.json
┃ ┣ 📜tsconfig.json
┃ ┣ 📜tsconfig.node.json
┃ ┣ 📜vite.config.ts
┃ ┗ 📜yarn.lock
┣ 📂raven_assistant
┃ ┣ 📂__pycache__
┃ ┃ ┗ 📜__init__.cpython-310.pyc
┃ ┣ 📂config
┃ ┃ ┗ 📜__init__.py
┃ ┣ 📂public
┃ ┃ ┣ 📂css
┃ ┃ ┣ 📂js
┃ ┃ ┗ 📜.gitkeep
┃ ┣ 📂raven_assistant
┃ ┃ ┗ 📜__init__.py
┃ ┣ 📂templates
┃ ┃ ┣ 📂includes
┃ ┃ ┣ 📂pages
┃ ┃ ┃ ┗ 📜__init__.py
┃ ┃ ┗ 📜__init__.py
┃ ┣ 📂www
┃ ┣ 📜__init__.py
┃ ┣ 📜hooks.py
┃ ┣ 📜modules.txt
┃ ┗ 📜patches.txt
┣ 📜.gitignore
┣ 📜README.md
┣ 📜license.txt
┣ 📜package.json
┗ 📜pyproject.toml
❯ bench build --app raven_assistant
[Errno 2] No such file or directory: '/home/frappe/moo-bench/apps/doppio/node_modules' -> './assets/doppio/node_modules'
Cannot link /home/frappe/moo-bench/apps/doppio/node_modules to ./assets/doppio/node_modules
✔ Application Assets Linked
yarn run v1.22.22
$ node esbuild --apps raven_assistant --run-build-command
File Size
DONE Total Build Time: 380.616ms
WARN Cannot connect to redis_cache to update assets_json
WARN Cannot connect to redis_cache to update assets_json
WARN Cannot connect to redis_cache to update assets_json
Running build command for raven_assistant
$ cd frontend && yarn build
$ vite build --base=/assets/raven_assistant/frontend/ && yarn copy-html-entry
vite v6.2.3 building for production...
✓ 32 modules transformed.
../raven_assistant/public/frontend/index.html 0.63 kB │ gzip: 0.37 kB
../raven_assistant/public/frontend/assets/react-CHdo91hT.svg 4.13 kB │ gzip: 2.05 kB
../raven_assistant/public/frontend/assets/index-DBSMtF5M.css 12.07 kB │ gzip: 3.25 kB
../raven_assistant/public/frontend/assets/index-c7EuIF-P.js 294.87 kB │ gzip: 95.22 kB
✓ built in 2.50s
$ cp ../raven_assistant/public/frontend/index.html ../raven_assistant/www/frontend.html
Done in 8.17s.
Compiling translations for raven_assistant
bench add-spa
Dashboard Name [dashboard]: frontend
App Name: raven_assistant
Which framework do you want to use? (vue, react) [vue]: react
Configure TypeScript? [y/N]: y
Generating spa...
Scaffolding React project...
yarn create v1.22.22
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Installed "create-vite@6.3.1" with binaries:
- create-vite
- cva
[##] 2/2│
◇ Scaffolding project in /home/frappe/moo-bench/apps/raven_assistant/frontend...
└ Done. Now run:
cd frontend
yarn
yarn dev
Done in 0.53s.
Installing dependencies...
yarn add v1.22.22
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 175 new dependencies.
info Direct dependencies
├─ @eslint/js@9.23.0
├─ @types/react-dom@19.0.4
├─ @types/react@19.0.12
├─ @vitejs/plugin-react@4.3.4
├─ eslint-plugin-react-hooks@5.2.0
├─ eslint-plugin-react-refresh@0.4.19
├─ eslint@9.23.0
├─ frappe-react-sdk@1.11.0
├─ globals@15.15.0
├─ react-dom@19.1.0
├─ react@19.1.0
├─ typescript-eslint@8.28.0
├─ typescript@5.7.3
└─ vite@6.2.3
info All dependencies
├─ @ampproject/remapping@2.3.0
├─ @babel/compat-data@7.26.8
├─ @babel/core@7.26.10
├─ @babel/generator@7.27.0
├─ @babel/helper-compilation-targets@7.27.0
├─ @babel/helper-module-imports@7.25.9
├─ @babel/helper-module-transforms@7.26.0
├─ @babel/helper-string-parser@7.25.9
├─ @babel/helper-validator-option@7.25.9
├─ @babel/helpers@7.27.0
├─ @babel/plugin-transform-react-jsx-self@7.25.9
├─ @babel/plugin-transform-react-jsx-source@7.25.9
├─ @babel/types@7.27.0
├─ @esbuild/linux-x64@0.25.1
├─ @eslint-community/eslint-utils@4.5.1
├─ @eslint-community/regexpp@4.12.1
├─ @eslint/config-array@0.19.2
├─ @eslint/config-helpers@0.2.0
├─ @eslint/eslintrc@3.3.1
├─ @eslint/js@9.23.0
├─ @eslint/object-schema@2.1.6
├─ @eslint/plugin-kit@0.2.7
├─ @humanfs/core@0.19.1
├─ @humanfs/node@0.16.6
├─ @humanwhocodes/module-importer@1.0.1
├─ @humanwhocodes/retry@0.4.2
├─ @jridgewell/resolve-uri@3.1.2
├─ @jridgewell/set-array@1.2.1
├─ @jridgewell/sourcemap-codec@1.5.0
├─ @nodelib/fs.scandir@2.1.5
├─ @nodelib/fs.stat@2.0.5
├─ @nodelib/fs.walk@1.2.8
├─ @rollup/rollup-linux-x64-gnu@4.37.0
├─ @rollup/rollup-linux-x64-musl@4.37.0
├─ @types/babel__core@7.20.5
├─ @types/babel__generator@7.6.8
├─ @types/babel__template@7.4.4
├─ @types/babel__traverse@7.20.7
├─ @types/estree@1.0.7
├─ @types/react-dom@19.0.4
├─ @types/react@19.0.12
├─ @typescript-eslint/eslint-plugin@8.28.0
├─ @typescript-eslint/parser@8.28.0
├─ @typescript-eslint/type-utils@8.28.0
├─ @vitejs/plugin-react@4.3.4
├─ acorn-jsx@5.3.2
├─ acorn@8.14.1
├─ ansi-styles@4.3.0
├─ argparse@2.0.1
├─ asynckit@0.4.0
├─ axios@1.8.4
├─ brace-expansion@1.1.11
├─ braces@3.0.3
├─ browserslist@4.24.4
├─ call-bind-apply-helpers@1.0.2
├─ callsites@3.1.0
├─ caniuse-lite@1.0.30001707
├─ chalk@4.1.2
├─ color-convert@2.0.1
├─ color-name@1.1.4
├─ combined-stream@1.0.8
├─ concat-map@0.0.1
├─ convert-source-map@2.0.0
├─ cross-spawn@7.0.6
├─ csstype@3.1.3
├─ deep-is@0.1.4
├─ delayed-stream@1.0.0
├─ dequal@2.0.3
├─ dunder-proto@1.0.1
├─ electron-to-chromium@1.5.128
├─ engine.io-client@6.5.4
├─ engine.io-parser@5.2.3
├─ es-define-property@1.0.1
├─ es-object-atoms@1.1.1
├─ es-set-tostringtag@2.1.0
├─ esbuild@0.25.1
├─ escalade@3.2.0
├─ escape-string-regexp@4.0.0
├─ eslint-plugin-react-hooks@5.2.0
├─ eslint-plugin-react-refresh@0.4.19
├─ eslint-scope@8.3.0
├─ eslint@9.23.0
├─ espree@10.3.0
├─ esquery@1.6.0
├─ esrecurse@4.3.0
├─ esutils@2.0.3
├─ fast-deep-equal@3.1.3
├─ fast-glob@3.3.3
├─ fast-json-stable-stringify@2.1.0
├─ fast-levenshtein@2.0.6
├─ fastq@1.19.1
├─ file-entry-cache@8.0.0
├─ fill-range@7.1.1
├─ find-up@5.0.0
├─ flat-cache@4.0.1
├─ flatted@3.3.3
├─ follow-redirects@1.15.9
├─ form-data@4.0.2
├─ frappe-js-sdk@1.8.0
├─ frappe-react-sdk@1.11.0
├─ gensync@1.0.0-beta.2
├─ get-intrinsic@1.3.0
├─ get-proto@1.0.1
├─ glob-parent@6.0.2
├─ globals@15.15.0
├─ graphemer@1.4.0
├─ has-flag@4.0.0
├─ has-symbols@1.1.0
├─ has-tostringtag@1.0.2
├─ ignore@5.3.2
├─ import-fresh@3.3.1
├─ imurmurhash@0.1.4
├─ is-extglob@2.1.1
├─ is-glob@4.0.3
├─ is-number@7.0.0
├─ isexe@2.0.0
├─ js-tokens@4.0.0
├─ js-yaml@4.1.0
├─ jsesc@3.1.0
├─ json-buffer@3.0.1
├─ json-schema-traverse@0.4.1
├─ json-stable-stringify-without-jsonify@1.0.1
├─ json5@2.2.3
├─ keyv@4.5.4
├─ locate-path@6.0.0
├─ lodash.merge@4.6.2
├─ lru-cache@5.1.1
├─ math-intrinsics@1.1.0
├─ merge2@1.4.1
├─ micromatch@4.0.8
├─ mime-db@1.52.0
├─ mime-types@2.1.35
├─ nanoid@3.3.11
├─ node-releases@2.0.19
├─ optionator@0.9.4
├─ p-limit@3.1.0
├─ p-locate@5.0.0
├─ parent-module@1.0.1
├─ path-exists@4.0.0
├─ path-key@3.1.1
├─ picomatch@2.3.1
├─ postcss@8.5.3
├─ proxy-from-env@1.1.0
├─ punycode@2.3.1
├─ queue-microtask@1.2.3
├─ react-dom@19.1.0
├─ react-refresh@0.14.2
├─ react@19.1.0
├─ resolve-from@4.0.0
├─ reusify@1.1.0
├─ rollup@4.37.0
├─ run-parallel@1.2.0
├─ scheduler@0.26.0
├─ shebang-command@2.0.0
├─ shebang-regex@3.0.0
├─ socket.io-client@4.7.1
├─ socket.io-parser@4.2.4
├─ source-map-js@1.2.1
├─ strip-json-comments@3.1.1
├─ supports-color@7.2.0
├─ swr@2.3.3
├─ to-regex-range@5.0.1
├─ type-check@0.4.0
├─ typescript-eslint@8.28.0
├─ typescript@5.7.3
├─ update-browserslist-db@1.1.3
├─ uri-js@4.4.1
├─ use-sync-external-store@1.5.0
├─ vite@6.2.3
├─ which@2.0.2
├─ word-wrap@1.2.5
├─ ws@8.17.1
├─ xmlhttprequest-ssl@2.0.0
├─ yallist@3.1.1
└─ yocto-queue@0.1.0
Done in 11.95s.
Wrote to /home/frappe/moo-bench/apps/raven_assistant/package.json:
{
"name": "raven_assistant",
"version": "1.0.0",
"description": "Using Thai tax expert assistant from OpenAI in raven",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Run: cd /home/frappe/moo-bench/apps/raven_assistant/frontend && npm run dev
to start the development server and visit: http://<site>:8080
📦raven_assistant
┣ 📂raven_assistant
┃ ┣ 📂__pycache__
┃ ┃ ┗ 📜__init__.cpython-310.pyc
┃ ┣ 📂config
┃ ┃ ┗ 📜__init__.py
┃ ┣ 📂public
┃ ┃ ┣ 📂css
┃ ┃ ┣ 📂js
┃ ┃ ┗ 📜.gitkeep
┃ ┣ 📂raven_assistant
┃ ┃ ┗ 📜__init__.py
┃ ┣ 📂templates
┃ ┃ ┣ 📂includes
┃ ┃ ┣ 📂pages
┃ ┃ ┃ ┗ 📜__init__.py
┃ ┃ ┗ 📜__init__.py
┃ ┣ 📂www
┃ ┣ 📜__init__.py
┃ ┣ 📜hooks.py
┃ ┣ 📜modules.txt
┃ ┗ 📜patches.txt
┣ 📜.gitignore
┣ 📜README.md
┣ 📜license.txt
┗ 📜pyproject.toml
@ManotLuijiu
Copy link
Author

@ManotLuijiu
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment