nextjs · backend · deploy · architecture
Next.js Background Workers in One Repo: No Monorepo
Skip the Turborepo and Docker tax. Share database models and types with your background worker in the exact same Next.js repo.

You wanted to add background workers to your Next.js application.
You opened a Reddit thread, and the answers overwhelmed you.
To run a worker, they said, you must convert your clean repository into a Turborepo monorepo. You need an apps/web folder, an apps/worker folder, and a packages/database shared workspace.
You need separate build scripts, custom tsconfig files, and multiple Docker containers.
What should have taken twenty minutes turned into three days of build configuration.
The monorepo tax for early-stage teams
Monorepos make sense when you have eighty engineers working across six distinct deployment targets.
When you are a solo founder or small team building a software business, a monorepo is pure friction.
Every time you change a database column, you must recompile your shared package. Every time you import a helper function, you fight path resolution and package exports.
Worst of all, your CI/CD pipeline becomes slow and brittle. Build times balloon from twenty seconds to three minutes because of monorepo dependency caching.
You do not need a monorepo to run background jobs. You just need a sane directory layout inside your existing repository.
The root of the problem: Module resolution
Why do tutorials push developers toward monorepos in the first place?
The friction comes down to how Next.js bundles code versus how plain Node.js executes scripts.
Next.js uses a compiler (Webpack or Turbopack) that handles path aliases like @/components and @/libs. It strips TypeScript types and bundles assets automatically.
When you try to run a standalone worker script using node worker.js, plain Node does not understand Next.js path aliases. It crashes with Cannot find module '@/libs/db'.
Developers get stuck, assume plain Node cannot touch their app code, and reach for monorepos to compile the worker independently.
There is a much simpler way.
The zero-monorepo pattern: Plain Node with relative imports
You can run background workers directly inside your existing Next.js repository using standard Node.js capabilities.
The secret is simple: keep your worker scripts pure, self-contained, and written with native ECMAScript modules (ESM) using relative imports.
In BoringPush, all background jobs live in scripts/jobs/<group>/<name>.mjs:
my-saas/
├── app/ # Next.js pages and API routes
├── libs/ # Core utilities (shared)
│ ├── boringqueue/ # Queue engine & worker pool
│ └── resend.js # Email transport
├── providers/db/ # Database models & queries
│ └── sqlite/ # Single-file database handles
├── scripts/
│ └── jobs/ # Self-contained worker jobs
│ ├── emails/
│ │ └── welcome.mjs
│ └── billing/
│ └── sync-receipt.mjs
└── package.jsonInside your background job script, use standard relative paths:
// scripts/jobs/emails/welcome.mjs
import { sendEmail } from "../../../libs/resend.js";
import { User } from "../../../providers/db/index.js";
export default async function (payload, { job, signal }) {
const user = await User.findById(payload.userId);
if (!user) return;
await sendEmail({
to: user.email,
subject: "Welcome to our platform",
text: `Hello ${user.name}, your account is ready.`,
});
}Notice what is happening here:
First, this file uses the exact same database models (User) and utility libraries (sendEmail) that your Next.js routes use.
Second, it does not require a separate build step or compilation script. Node runs it directly.
Third, changes to your database schema or utility files are immediately visible to your workers without recompiling packages.
Managing the worker process with PM2
In development and production, your worker loop runs alongside your Next.js application.
In BoringPush, process management is handled by PM2 using ecosystem.config.cjs:
module.exports = {
apps: [
{
name: "web",
script: "node_modules/next/dist/bin/next",
args: "start",
env: { NODE_ENV: "production", PORT: 3000 },
},
{
name: "worker",
script: "libs/boringqueue/worker.mjs",
env: { NODE_ENV: "production" },
restart_delay: 2000,
},
],
};When you deploy your application, both processes start together.
Your web application accepts HTTP traffic on port 3000. Your worker process continuously claims and executes tasks from db/queue.db.
If the worker ever encounters an unhandled exception, PM2 resurrects it automatically within two seconds.
Zero Docker containers. Zero monorepos. One single git repository.
Running workers during local development
Running workers on your development laptop is equally straightforward.
You run your Next.js development server in one terminal tab:
npm run devAnd in a second terminal tab, you start the worker loop:
npm run bp -- jobs workWhen you dispatch a job from an API route or page, the worker picks it up and logs output directly in your second terminal.
You can set breakpoints, read console logs, and inspect execution speed in real time.
Simplicity is an operational advantage
Every time you add a build tool or package manager to your codebase, you introduce maintenance debt.
You have to upgrade dependencies in multiple package.json files. You have to debug why hot module reloading stopped working across workspace boundaries.
By keeping your background jobs in the same repository as plain Node scripts, your codebase remains transparent.
Any engineer or AI coding agent can inspect scripts/jobs/, understand how data flows, and make safe changes without navigating monorepo configuration files.
Build features, not build pipelines.
FAQ
Can my worker jobs import TypeScript files directly?
If your Next.js app uses TypeScript, you can use Node's native strip-types flag or use .mjs for worker scripts. In BoringPush, keeping worker scripts as standard JavaScript means Node executes them natively with zero compile delay.
What happens if I update my database schema?
Because the worker script imports the same model files as your Next.js routes, schema updates are immediately reflected in both the web application and the worker processes upon restart.
Does the worker share memory with the Next.js web server?
No. The web server and the worker script run as independent operating system processes. They communicate exclusively through the durable SQLite queue table, ensuring complete fault isolation.
Can I run multiple worker processes for higher concurrency?
Yes. In your PM2 ecosystem file, you can set the instances parameter on the worker process to spawn multiple parallel worker loops that pull from the same queue table.
How do I deploy this setup?
A single git push production main or standard deploy script deploys the repository. The deploy script builds the Next.js production bundle, reloads PM2, and both the web app and worker update atomically.
Keep reading
- Next.js Background Jobs Without Redis: $0 BullMQ — the full queue engine
- Why Next.js Background Tasks Die in Production — solving serverless freezes
- PM2 Zero-Downtime Deploys on a $5 VPS — managing Node processes
+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)
Command from your phone.
Built so autonomous AI agents build what you want without breaking what's already paying you.
- Built-in analytics — cookie-free mode, $0 forever, no PostHog bill
- SQLite ↔ Supabase: one setting, same field names
- Full digital marketplace & feedback boards included
- 3 payment rails: Stripe, LemonSqueezy & Dodo Payments
- Agent fences: lint gate, read-only data access, auto-rollback
- Ship from your phone via SSH & WireGuard/Tailscale in under a minute
$199
Get BoringPushOne-time payment · Lifetime updates · Unlimited projects.
Keep reading


