Beyond the core
Nift with the wider web toolchain.
This is the practical integration guide: keep Nift responsible for tracked outputs, composition, paths and dependencies, then connect TypeScript, Vite, Tailwind, component frameworks, backends and other specialist tools through ordinary files and build scripts.
If another tool can produce or consume files, it can usually sit beside Nift directly. For the design rationale behind that boundary, see Why Nift?
Plain HTML + CSS + JavaScript
The simplest integration is no integration at all: keep ordinary assets in the public tree and reference them from Nift templates.
<link rel="stylesheet" href="@pathto('public/assets/css/style.css')">
<script src="@pathto('public/assets/app.js')"></script> Using @pathto for those concrete local paths gives generated pages the correct relative URL and lets Nift fail the build when the referenced file does not exist.
npm or Bun + TypeScript
Package management and TypeScript compilation can remain completely conventional. Compile TypeScript into Nift's public assets, then reference the result.
{
"scripts": {
"build:js": "tsc",
"build:site": "nift build",
"build": "npm run build:js && npm run build:site"
}
} <script type="module" src="@pathto('public/assets/js/app.js')"></script> With Bun the same idea can use bun run or bun build. Nift does not care which package manager produced the asset.
Vite as the frontend asset pipeline
Vite can own JavaScript/TypeScript bundling and development-oriented frontend tooling while Nift owns page composition.
project/
content/
templates/
frontend/
src/
package.json
vite.config.js
public/
assets/
.nift/ npm --prefix frontend run build
nift build Configure Vite's output where it makes sense for your project—commonly somewhere under public/assets/—and Nift templates can reference those built files normally.
Tailwind CSS
Tailwind can scan the HTML/templates you actually author and emit CSS into the output tree. Nift only needs to link to the generated stylesheet.
npx @tailwindcss/cli -i ./assets/input.css -o ./public/assets/css/site.css
nift build <link rel="stylesheet" href="@pathto('public/assets/css/site.css')"> React, Vue or Svelte where you want richer UI
Nift does not require the whole page to become a client framework application. A bundler can produce a React/Vue/Svelte entry point for a particular interactive region while Nift continues generating the surrounding document.
<main>
@content
</main>
<div id="account-dashboard"></div>
<script type="module" src="@pathto('public/assets/dashboard.js')"></script> The component framework owns the interactive region. Nift owns the document structure around it. If your application genuinely wants React/Vue/Svelte to own nearly everything, Nift can instead be used for the portions where its templating/build model remains useful.
Go backend
my-app/
content/
templates/
public/
assets/
backend/
main.go
api/
.nift/ Nift can generate the frontend files that your Go service serves, while browser JavaScript talks to the backend API in the usual way.
const response = await fetch('/api/status');
const status = await response.json(); No Nift-specific Go integration is required.
Node + Express
An Express application can serve Nift's public directory and provide APIs beside it.
import express from 'express';
const app = express();
app.use(express.static('public'));
app.get('/api/health', (req, res) => {
res.json({ ok: true });
});
app.listen(3000); Python backends
The same boundary works with Flask, FastAPI, Django or another Python stack: generate frontend assets/pages with Nift, serve them from the Python application or a web server/CDN, and expose APIs normally.
Nift: templates + pages + static frontend output
Python: application runtime + APIs + database + authentication
Browser: ordinary HTTP between them Minifiers, image tools and bundlers
Tools such as esbuild, Rollup, SWC, terser, lightningcss, PostCSS, Sass, image optimisers and custom scripts can run before or after Nift depending on what they transform.
# One possible production pipeline
npm run build:frontend
nift build --all
npm run minify:html There is no requirement for a “Nift plugin” merely to use a tool. If it can read and write files, it can usually compose with Nift.
Why Nift does not need an @asset(...) primitive
A generic asset helper sounds attractive until its responsibility is made precise. It would usually be asked to do some combination of:
Produce a browser URL
Nift already does this with @pathto(...), including project-aware relative paths.
Check that a local asset exists
@pathto(...) already fails when its concrete project target is missing.
Make the page rebuild when another file changes
That is a different concern. Use @dep(...) only when the file genuinely influences generated output and Nift cannot infer the relationship another way.
Build/bundle the asset
That belongs to Vite, esbuild, TypeScript, Sass, an image pipeline or whichever specialist tool actually owns the transformation.
Combining those jobs into @asset would make a short expression but blur useful architectural boundaries. It could also imply that Nift owns when or how frontend assets are generated. Keeping path generation and dependency declaration separate is more explicit:
<script type="module" src="@pathto('public/assets/account.js')"></script> @dep('data/account-build-input.json') The first says “this page links to this concrete browser asset.” The second, when actually needed, says “this page's generated output depends on this otherwise-unseen input.” They are not inherently the same relationship.
Why build orchestration belongs in build scripts, not Nift hooks
Nift deliberately does not need configuration such as beforeBuild, afterBuild or arbitrary shell execution. A project-level build script, Makefile, package script, task runner or CI workflow is a better orchestration layer because it can coordinate all of the project's tools rather than making Nift the parent process of everything else.
{
"scripts": {
"build:frontend": "vite build",
"build:nift": "nift build --all",
"build": "npm run build:frontend && npm run build:nift"
}
} A Makefile can express the same boundary without involving npm:
site:
\t$(MAKE) -C frontend build
\tnift build --all
serve: site
\t./server This is at least as powerful as Nift-specific hooks and usually more powerful. The orchestration layer can:
- run tools before or after Nift;
- run independent jobs in parallel;
- stop immediately when any step fails;
- set environment variables and deployment modes;
- build backend code, frontend bundles, images and Nift pages in one graph;
- use the same commands locally and in CI;
- replace one specialist tool without changing Nift configuration.
Shell execution inside the generator would enlarge the security surface, add platform-specific quoting/process behaviour and encourage project logic to hide in Nift configuration. Build scripts already solve orchestration in a more general place.
When @dep actually belongs
Do not add @dep just because another tool produced a CSS or JavaScript file that you reference with @pathto. Use @dep for the narrower case where an external file influences a generated output but Nift otherwise has no relationship from which to know that.
@dep('data/generated-navigation.json') A repository can contain several worlds
product/
site/ # Nift content/templates
frontend/ # TypeScript + Vite + React
server/ # Go / Node / Python
scripts/ # build/minification/image tooling
public/ # assembled web output
package.json
Makefile Your top-level build script, Makefile, package scripts or CI workflow can orchestrate those pieces. That orchestration layer can call Nift, a bundler, a backend compiler and deployment tooling in whatever order the project requires. Nift does not need shell hooks or plugin wrappers to participate.
Use the best tool for each job. Nift's job is to make the web-facing files easy to compose, track and build without forcing the rest of the repository into a Nift-specific architecture.