Mastering Dependency Decisions in Software Projects

Published on June 12, 2025

Mastering Dependency Decisions in Software Projects

We all want to develop features quickly. We don't want to redevelop a solution to a problem that has already been solved and battle-tested by someone else. So we often turn to an existing package - you know, browsing NPM!


Adding an external package is a double-edged sword, as it comes with its own risks as well - extra bundle size, security vulnerabilities, breaking changes, etc.

In order to tackle this dependency management, I will show you exactly:


- How you can assess the necessity of a new package

- What are the best practices

- How you can set rules in your team

- How you can make sure rules are followed


Article content

How Do You Keep Your Dependencies Under Control?

First step to successfully managing dependencies is *drum rolls* - not to have any! But that doesn't happen often, does it? That would require us to reinvent everything and we don't want to do that. Avoiding dependencies entirely would require us to reinvent everything, and we don't want to do that. So the second best thing is to find a way how to assess, which dependency we will add to our project and which not.

Let’s look at the best practices for deciding whether a particular dependency is needed in our project.


Best Practices For Evaluating the Need For a New Dependency

Necessity of the Dependency

We need to assess whether dependency is truly necessary. We can do this by asking ourselves following questions:


Ease of implementation - Can the functionality be implemented reasonably, without adding external package? Ask your peers, senior members of the team, or even ChatGPT!


Native solution - Do native solutions exists? Whether it is in your existing codebase, existing library or current native browser features. Remember to also check capabilities of frameworks you use.


Evaluate the need - Dependencies should add significant value or solve complex problems, not just address easily solved issues in-house.


Quality And Long-Term Viability

Once you decide a dependency is needed, it's crucial to ensure it meets a certain level of quality and offers long-term reliability. The last thing you want is to add a new dependency that becomes unsupported shortly after, forcing you to replace it. So how do you quantify quality and long-term viability?


Active Maintenance - this one is easy to spot, and most of us are already doing it. When was the latest version of the package published? What about previous versions. Are they released regularly? Weekly downloads? Most of this data is already visible on NPM page of the specific package.


Community Support - Is it only creators of the package who contribute to new features and maintenance? Or are other developers also contributing? Check GitHub page of the package and "Issues" section. Look at open and closed issues, to understand how responsive the maintainers are. Remember that active user community is a strong indicator of package reliability and longevity.


Up-to-date Documentation - last but not least is documentation. Is it comprehensive, clear and up-to-date? Usually, these are signs of well-maintained package. Clear installation instructions, usage guides, examples and API references are must have.


License Compliance

It's easy to install a package and just use it. But you need to be aware that it can have legal implications. Even more so, when your software is used for commercial purposes. Therefore, checking a license of a package is essential before adding it to your project. Here is the brief list of most common incenses. (Disclaimer: do your own license research, for your particular case)


MIT License (details) - most permissive and commonly used. It allows you to do almost anything with the code (including using it in commercial projects) as long as the original license and copyright notice are included with any substantial portions of the software. This is commonly used for JavaScript libraries.


ISC License (details) - similar to MIT license, it is permissive free software license. It allows for commercial use, modification, distribution and private use. Requires including the full text of license in modified software.


Apache License 2.0 (details) - also similar to MIT license in it's permissiveness. Although, requires modified versions to state the changes made, when distributing software.


Proprietary Licenses - some packages might be under proprietary licenses, where the copyright holder maintains control over the use and distribution of the software. These often come with more restrictions, especially for commercial use.


It's good practice to consult with legal counsel when incorporating open-source software into commercial projects, especially if you're dealing with a variety of licenses or large codebases.


Security

There is no such a thing as bulletproof package. So when you decide to add a new one to your code, you are opening yourself to potential vulnerabilities. Therefore it's crucial to assess the current state of the dependency. Here is how you can do it:


Vulnerability Scanning - if you are using NPM package manager you can run command npm audit, which asks for report of known vulnerabilities of your packages, and if any are found, then follow steps which can be taken to fix those. Alternatively, you can use more comprehensive tools such as Snyk.


Dependency Pinning - if you know the package well, and you are happy with it as it is, you can also pin a version of package to avoid automatically updating to newer versions, which might introduce new vulnerabilities. However, this needs to be balanced with the need to update for security patches.


Regular Updates - many security vulnerabilities are fixed in newer versions. Therefore regularly updating is important. So if your repository is on GitHub, you can take advantage of Dependabot and configure it to check your dependencies regularly, and make pull requests for any new versions.


Checking Deprecated Functions - ensure that dependency does not use deprecated or unsafe functions, which can be removed in future releases, or are not maintained anymore.


Automating The Best Practices

Having rules and guidelines is a great first step. But how do you make sure they are followed? How do you do it with as little overhead as possible?

.github/CODEOWNERS

In order to be aware what dependencies are being added or removed, you can specify a person or a team members, which will need to approve any changes in regards to dependencies in your project, such as any changes in your package.json file.

In your root of the repository create a folder called .github and inside of it a file called CODEOWNERS. Here you can specify a rules you want. For example if you want to require a specific team member approval for any changes in package.json file you can do the following:


// Inside .github/CODEOWNERS
**/package.json @username

This rule will apply to all package.json files in your repo. Approval of @username will be required for PR to be merged.


Dependabot

In order to automate dependencies with Dependabot, we need to configure it. We do this by creating root of our project, inside .github folder a dependabot.yml file.


# dependabot.yml configuration file

version: 2
updates:
# Package manager to be used
- package-ecosystem: "npm"
# Look through all directories
directory: "/"
schedule:
# daily | weekly | monthly
interval: "weekly"
open-pull-requests-limit: 10
ignore:
# For all packages, ignore all patch updates
- dependancy-name: "*"
update-types: ["version-update:semver-patch"]


With this configuration file, we use npm as package manager. Dependabot looks through all directories and check package updates on weekly basis. It will open maximum 10 pull requests at a time.


If you have a big project and you didn't have Dependabot before, I suggest setting a rule of ignoring "patch" versions on dependencies in the beginning so you can focus on major and minor versions. And once you have all dependencies up to date, you can remove that rule. As often patch versions contains bugfixes and security patches.

Bundlephobia

If you want to find out performance impact of your npm packages and it's effect on your bundle size or see historical trends, then this tool is for you. You can either use it online, by searching for specific package name, or you can upload your package.json file.

License Scanning

As I already mentioned, you need to be aware of the licenses associated with the packages you use. You can utilize a tool like FOSSA to help you protect your software against license violations. Additionally, you can achieve continuous compliance by integrating it into your CI pipeline.

Conclusion

Managing project dependencies in a lean and clean manner is essential for efficient development of software. While leveraging external libraries can accelerate feature development, it's crucial to navigate this path with a strategic approach. By assessing the necessity, quality, and long-term viability of each dependency, ensuring compliance with licensing, and maintaining robust security protocols, you can significantly mitigate the risks of added dependencies.


Embracing best practices, setting clear team rules, and utilizing tools for automation and monitoring are key steps to maintaining a healthy dependency ecosystem. Remember, the goal isn't just to add features rapidly but to build sustainable, secure, and efficient software that stands the test of time.

More articles

Agent Skills for SaaS Development: Three SKILL.md Workflows for Auth, Billing, and Release Checks

Agent Skills for SaaS Development: Three SKILL.md Workflows for Auth, Billing, and Release Checks

Agent Skills are most useful when a task has a repeatable definition of done that is too detailed for a repository-wide instruction file. For a SaaS codebase, that usually means high-consequence work such as authentication, billing, or release verification. A good Skill tells an agent what to inspect, which boundaries it must preserve, which checks to run, and when to stop and ask for review.This article gives you three small, copyable templates. They are deliberately narrow: they do not try to make an agent autonomous, and they do not replace code review or product-specific engineering. They make recurring work more consistent.What a Skill adds beyond an agent contractUse an AGENTS.md or equivalent repository contract for rules that apply to nearly every change: architecture, TypeScript conventions, protected areas, testing commands, and secret handling. Use a Skill for an infrequent but consequential workflow that needs deeper instructions only when it is relevant.The Agent Skills specification defines a Skill as a directory with a SKILL.md file, with optional scripts, references, and assets. GitHub’s current documentation makes the same distinction: custom instructions suit broad, always-relevant guidance, while Skills package detailed instructions and resources for specialized tasks. See the Agent Skills specification and GitHub’s guidance on adding Skills.A simple decision rulePut it in the agent contract when the rule applies to most work: feature boundaries, strict typing, default Server Components, or where secrets may live.Put it in a Skill when the task has a distinct input, review sequence, and verification command: changing sign-in behavior, touching payment-state code, or preparing a release.Keep it in human review when the decision needs product judgment, security sign-off, production access, or an irreversible action.The goal is not a bigger prompt. It is a small operational interface: a name, an activation condition, a short procedure, concrete evidence to collect, and a safe failure path.The reusable Skill shapeEach template below follows the same structure. The frontmatter lets an agent identify the workflow; the body says what success requires; and a small script gathers evidence without making a production change. Treat the paths and commands as placeholders: adapt them to the architecture and package scripts your repository actually has.skill-name/ SKILL.md scripts/ inspect-change.mjs references/ review-checklist.mdKeep the entry point short. The Agent Skills specification recommends moving detailed material into referenced files so the agent can load it only when needed. That keeps normal tasks from carrying release or payment instructions they do not need.Skill 1: review an authentication changeUse when: a change affects login, callbacks, session creation, account recovery, protected routes, or role checks. The immediate problem is that a visually correct sign-in change can still weaken a server-side access decision. The outcome is a reviewable change with explicit authentication and authorization checks.SKILL.md---name: auth-change-reviewdescription: Review authentication and authorization changes before merge.---# Authentication change reviewUse this Skill only when a task changes sign-in, sessions, protected routes,account recovery, or authorization.1. Inspect the changed route, action, and nearest existing auth pattern.2. List the authenticated actor, required role, and server-side enforcement point.3. Confirm client components do not import server-only auth or database code.4. Add or update a focused failure-path test.5. Run the repository’s relevant lint and test commands.6. Stop for human review if the change alters provider configuration, session lifetime, recovery policy, or production secrets.Run scripts/inspect-change.mjs before reporting completion.scripts/inspect-change.mjsimport { execFileSync } from "node:child_process";const files = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8",}).trim().split("\n").filter(Boolean);const sensitive = files.filter((file) => /(auth|session|callback|middleware|protected|role)/i.test(file),);console.log(JSON.stringify({ changedFiles: files, authRelatedFiles: sensitive }, null, 2));if (sensitive.length === 0) process.exitCode = 2;This script does not validate security. It only makes the scope visible, so the agent cannot quietly claim it reviewed an authentication change without naming the relevant files. A nonzero exit is a prompt to inspect the task, not a license to bypass the check.Skill 2: protect payment boundariesUse when: a change touches checkout, webhooks, entitlements, refunds, subscription state, or a payment provider adapter. The problem is not merely calling a provider API; it is preserving the relationship between a verified event, a durable internal state, and the user-facing result.SKILL.md---name: billing-boundary-reviewdescription: Review SaaS billing changes for provider boundaries and event state.---# Billing boundary reviewUse this Skill for checkout, webhook, refund, entitlement, or subscription work.1. Identify the external event, the internal state transition, and the actor allowed to initiate the change.2. Keep provider SDK calls behind the existing payment adapter.3. Document duplicate-event behavior and the expected retry result.4. Add a focused test for one valid transition and one rejected or duplicate path.5. Run scripts/list-billing-files.mjs and the project’s relevant checks.6. Stop if the change requires a live payment action, real customer data, or a policy decision about refunds or access.scripts/list-billing-files.mjsimport { execFileSync } from "node:child_process";const output = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8",});const files = output.split("\n").filter((file) => /(billing|payment|checkout|webhook|subscription|entitlement)/i.test(file),);console.log(files.length ? files.join("\n") : "No billing-related files found.");if (!files.length) process.exitCode = 2;For example, Frontend Accelerator’s verified architecture keeps payment behavior behind a PaymentAdapter, while its canonical agent contract says features should not import payment-provider SDKs directly. That is the kind of local rule a billing Skill should reinforce, not replace.Skill 3: verify a release candidateUse when: a feature or bug fix is ready for a release-candidate review. The reader problem is a familiar one: a passing implementation check does not prove that documentation, environment configuration, routes, and the release artifact still line up. The outcome is a concise evidence bundle a human can review.SKILL.md---name: release-candidate-checkdescription: Gather release evidence without publishing or changing production.---# Release candidate checkUse only for a proposed release. Do not publish, deploy, or modify production.1. Read the release scope and identify affected user flows.2. Run the documented lint, test, and build checks that apply to the change.3. Inspect environment-variable documentation for any new server-side setting.4. Record the exact commands, result, and any skipped check with a reason.5. Run scripts/release-evidence.mjs.6. Stop for a human if a check fails, production credentials are required, or the release includes a migration, payment policy, or security-sensitive change.scripts/release-evidence.mjsimport { execFileSync } from "node:child_process";const status = execFileSync("git", ["status", "--short"], { encoding: "utf8",}).trim();const head = execFileSync("git", ["rev-parse", "--short", "HEAD"], { encoding: "utf8",}).trim();console.log(JSON.stringify({ head, workingTree: status || "clean" }, null, 2));if (status) process.exitCode = 2;That final guard matters. An agent should report a dirty working tree or a failed verification step as evidence, not tidy it by resetting, stashing, or changing unrelated files.How to introduce Skills without instruction sprawlStart with one failure mode. Choose a workflow where repeated context loss is already costly, such as duplicate webhook handling or inconsistent authorization review.Write the stop conditions first. Name the actions the Skill must not take: deploy, change production data, use real customer records, expose secrets, or decide policy alone.Point to repository evidence. Link the Skill to existing adapters, test commands, and architecture documents. Do not copy a generic checklist that contradicts local conventions.Make the evidence machine-readable where practical. A small script that lists changed sensitive files is safer and more useful than a vague instruction to “check everything.”Review the Skill like code. Test its activation description against realistic prompts, inspect any script, and keep versioned dependencies or references explicit.Where Frontend Accelerator fitsFrontend Accelerator includes a canonical AGENTS.md contract, tool-specific agent entry points, and curated project Skills under .agents/skills/, tracked by skills-lock.json. Its architecture rules are intentionally broad: feature modules own business behavior, the app directory focuses on routing and composition, and provider SDKs stay behind adapters. That makes Skills useful as a focused second layer for tasks such as testing, Stripe work, caching, or framework upgrades.Recommendation: start from a codebase whose boundaries you can explain, then add Skills only where a repeatable workflow needs more operational detail. A Skill can improve consistency; it cannot make an unreviewed change safe by itself.Get the sample Skills packThe sample pack is the three copyable templates in this article. Put each in its own directory, replace the placeholder commands and paths with repository evidence, and test the stop conditions with a non-production task before relying on it for auth, billing, or release work.A practical first rollout: choose one skill, run it on a small pull request, and compare its evidence with a normal review. Keep it only if it catches a missed boundary, speeds a justified check, or makes the handoff clearer. If it merely repeats generic advice, remove it and keep the repository contract concise.SourcesAgent Skills SpecificationGitHub Docs: Adding agent skills for GitHub Copilot CLIGitHub Docs: Customizing Copilot responses

July 15, 2026Learn more
AGENTS.md for Next.js: a practical template for coding agents

AGENTS.md for Next.js: a practical template for coding agents

An AGENTS.md file should be a short operating contract for your coding agent: where code belongs, what not to touch casually, how to validate a change, and which existing patterns to copy. It is not a giant prompt, a substitute for review, or a guarantee that an agent will make correct changes.For a solo developer building a Next.js SaaS with an agent, the useful goal is simpler: make the safe, repeatable path obvious before the agent starts exploring. That reduces the odds of a small feature spreading business logic into pages, pulling provider SDKs into components, or skipping the checks that would expose a regression.This guide gives you a practical starting template, explains the sections that do real work, and shows how to keep the file honest as your application changes.What an AGENTS.md file should doRepository instructions are persistent project context. GitHub documents repository-wide and path-specific custom instructions, including AGENTS.md files for agent guidance; the closest file can take precedence when multiple instruction files exist. That makes placement and scope part of the design, not just a naming choice.For a Next.js SaaS, a good root file answers four questions before an agent edits anything:What is this project? Identify the framework, major boundaries, and the kind of product it is.Where does a change belong? Describe feature modules, shared code, routing, and infrastructure seams.What must remain protected? Name areas that need deliberate review, such as auth, payments, configuration, migrations, and provider adapters.How is a change proven? Give the commands and targeted checks that are appropriate for the repository.The file should reduce discovery cost. It should not claim that a test suite is comprehensive if it is not, promise production safety, or instruct an agent to bypass review for sensitive changes.The practical AGENTS.md templateStart with this template, then replace every bracketed item with facts verified in your repository. Delete any rule you cannot maintain. The point is a usable contract, not a long document.# Project Agent Contract## Project context[Product name] is a [framework and architecture] application for [user/job].Read the target module and nearby implementations before changing code.Prefer existing patterns over a new abstraction.## Architecture- Put product behavior in `src/features/<feature>/`.- Keep `app/` limited to routing, layouts, route handlers, and composition.- Do not import one feature directly from another feature.- Move code to `src/shared/` only when it is genuinely reused.- Access databases, payments, and AI providers through their existing adapters.## Change boundariesTreat these areas as protected infrastructure; change them only when the task requires it:- `src/lib/`- `src/config/`- `src/services/`- authentication, billing, webhook, and deployment configurationDo not expose secrets to client code. Keep provider SDKs out of UI components.## Implementation rules- Default to Server Components; add `"use client"` only for browser APIs or interactivity.- Preserve strict TypeScript; do not add `any` to escape a type error.- Validate inputs at server boundaries.- Return the project’s established result shape for expected action failures.- Reuse the repository’s UI primitives and accessibility conventions.## VerificationRun the checks relevant to the change:```bashnpm run lintnpm test -- --runInBandnpm run build```Before finishing, review the diff, preserve unrelated local changes, and statewhich checks ran and which did not.The commands above are deliberately examples. If your project uses Playwright, a package workspace command, a database emulator, or a different test runner, record the exact command that contributors can actually run. A validation section that always fails will quickly be ignored.Why the architecture section matters mostCoding agents can generate a route, form, or component quickly. The more expensive failure is a feature that looks correct in isolation but bypasses the application’s boundaries. For example, a client component that reaches directly into a database SDK creates a different security, caching, and testing path from the rest of the product.Use the architecture section to state relationships, not slogans. “Keep business logic in feature modules” is useful when paired with “keep app/ focused on routing and composition.” “Use adapters” becomes actionable when it names the boundary: features call the project’s database or payment abstraction rather than importing a vendor SDK.Frontend Accelerator’s current agent contract follows this pattern: it identifies feature-first modules, keeps routing and composition in app/, and keeps provider-specific implementation behind adapters. That is a concrete example of instructions describing a codebase’s real seams rather than asking an agent to be generally careful.Annotate the template before you adopt itUse this review checklist to turn the template into repository-specific guidance.Map one recent feature. Trace a small change from route to feature logic, data access, and test. Write only the boundaries that actually exist.Name protected infrastructure. Include the paths and domains where a casual edit can change security, billing, configuration, or deployment behavior. Do not mark half the repository as protected.Check the agent’s working directory. If you use nested AGENTS.md files, define which root rules still apply and what the local file adds. Avoid contradictory instructions.Use real commands. Run every listed lint, test, type-check, and build command from a clean enough local environment. Record prerequisites such as an emulator or required environment variables without putting secrets in the file.Test with a small task. Ask an agent to add a contained feature or fix a clear bug. Review whether it chose the intended directory, obeyed the boundary, and ran the stated verification.This is a maintenance loop. When you move a feature boundary, change a test command, or replace a provider integration, update the instruction that would otherwise mislead the next agent.Common failure modesA file that describes an idealized repository“All actions return ActionResult” is harmful if half of the existing actions throw exceptions. Either migrate the pattern deliberately or state the local convention precisely. Agents need a reliable default, not aspirational policy.A wall of rules with no priorityLong instruction files make it difficult to notice the rules that matter. Put the architecture, protected areas, and verification commands near the top. Link to longer design documents instead of duplicating every implementation detail.Commands that are too broad for every changeA full production build may be necessary before a release but disproportionate for a copy change. Describe a baseline for all changes and add targeted checks by area. An agent should still report skipped checks rather than implying they ran.Using instructions as a security boundaryAn AGENTS.md file can tell an agent to validate authorization and keep secrets server-side. It cannot enforce those rules at runtime. Keep real controls in code, CI, access policies, review, and tests.A lightweight operating model for solo buildersUse one canonical root AGENTS.md for rules that apply to the whole app. Keep tool-specific files small and point them back to that contract. Add a nested instruction file only when a directory has genuinely different constraints, such as a payment integration, generated client, or mobile app.Then make the agent’s handoff inspectable. A good task result says what changed, which architectural boundary it used, what it did not change, and which verification ran. That is more useful than a confident claim that the change is “done.”If you want a structured starting point, review Frontend Accelerator’s architecture and AI conventions. It is designed as a Next.js SaaS foundation with a canonical agent contract, tool-specific instruction entrypoints, and repository-level Skills; you still need to adapt any template to your own product decisions and review the generated changes.SourcesOpenAI Codex documentation: AGENTS.mdGitHub Docs: Adding repository custom instructions for GitHub Copilot

July 14, 2026Learn more
A Production-Ready Next.js SaaS Folder Structure for TypeScript, Firestore, and Stripe

A Production-Ready Next.js SaaS Folder Structure for TypeScript, Firestore, and Stripe

Use app/ for routes and composition, src/features/ for product behavior, src/lib/ for provider adapters, and src/shared/ only for code that is genuinely reusable. That split gives a Next.js SaaS a place to grow without turning page files into the place where authentication, billing, Firestore access, and UI state get tangled together.For a solo technical founder, the payoff is not an academic folder taxonomy. It is being able to ask an AI coding agent for a new SaaS capability, review the change in a predictable area, and keep Stripe or Firebase details out of unrelated features. The structure below is a real, current shape from the Frontend Accelerator product repository, adapted into a practical pattern rather than a promise that every SaaS needs the same folders.The folder structure to start withNext.js leaves project organization largely up to you: app is the App Router, public serves static assets, and src is an optional source folder. A route becomes accessible when a segment contains a page or route file. That flexibility is useful, but it also means a SaaS needs its own boundary rules before feature work accumulates.app/ [locale]/ localized marketing, auth, and protected routes api/ auth, lead-capture, payment, and webhook handlerssrc/ features/ auth, blog, changelog, dashboard, glossary, marketing, and profile behavior shared/ reusable components, hooks, layouts, and utilities lib/ auth, AI, database, email, and payment adapters config/ application and sitemap configuration i18n/ routing, request configuration, and messages services/ shared API and state services styles/ global styles and design tokens types/ global TypeScript declarationspublic/ static assetsThis is a feature-first structure, not a claim that folders alone make an app production-ready. The useful part is the dependency direction: route files compose; features own product behavior; provider integrations sit behind narrow infrastructure adapters.Architecture diagram: the dependency direction that mattersapp/[locale] and app/api │ route composition, request entry points ▼src/features/<feature> │ feature actions, components, validation, product rules ├──────────────► src/shared/ only cross-feature reuse ▼src/lib/<domain>/adapter │ provider-specific implementation remains here ▼Firestore / MongoDB / Stripe / Lemon Squeezy / AI providerThe arrow should not point directly from a feature to a provider SDK. In Accelerator, database work is accessed through a database adapter and repositories; payments through a payment adapter; and AI behavior through an AI adapter. The concrete Firestore, MongoDB, Stripe, Lemon Squeezy, OpenAI, Claude, and Gemini integrations live in adapter folders. That gives the rest of the application a smaller, more stable surface to depend on.What belongs in app/Keep app/ intentionally thin. It owns URL structure, layouts, route groups, route handlers, loading and error boundaries, and composition of the UI for a route. In the verified product repository, localized public, auth, and protected routes live under app/[locale]/; API routes cover authentication, lead capture, payments, and payment webhooks.Do not turn a page into a hidden service layer. A dashboard page can fetch the data it needs and compose feature components, but it should not grow a second copy of billing rules or scattered Firebase calls. Next.js route groups can organize layouts without affecting the URL, and private folders can colocate non-routable utilities when a route needs them. Use those framework features for route-local concerns; use feature modules for product behavior that must survive beyond one page.Server Components are the default boundaryIn an App Router project, start with Server Components and introduce "use client" only where hooks, event handlers, browser APIs, or client-only libraries require it. This matters for folder structure because a Client Component must not become a shortcut around server-only infrastructure. Keep database and payment setup on the server side, then pass narrow data and callbacks into interactive components.What belongs in src/features/A feature directory owns behavior that a user recognizes: authentication, the blog, a dashboard capability, or profile management. It can contain focused components, server actions, validation, types that are specific to the feature, and small local utilities. The key rule is that one feature should not reach into another feature’s internals. If code truly becomes reusable across independent features, promote it to src/shared/; otherwise keep it close to the behavior it supports.This is especially valuable when working with agents. A task such as “add a customer-facing subscription status to the dashboard” can be constrained to the dashboard feature, the payment boundary, and the relevant route. That is much safer to review than a broad instruction such as “update the dashboard everywhere.” The architectural rule becomes a practical review checklist.What belongs in src/lib/: provider boundariesTreat src/lib/ as protected infrastructure. In the product repository, it holds auth, AI, database, email, and payment domains. Provider-specific code stays below those domains’ adapter folders. Features do not import Stripe, Firebase, MongoDB, or another provider SDK directly.For example, a subscription feature should express what it needs from a payment adapter—such as creating a checkout session or reconciling a verified event—not how the Stripe SDK formats a request. A Firestore-backed feature should use the database adapter and repository shape rather than query a collection from a component. This is not abstraction for its own sake: it localizes a provider change, makes tests more targeted, and prevents the client bundle from accidentally reaching server credentials.An illustrative addition workflowDefine the reader-visible behavior in the relevant src/features/<feature> directory.Decide whether the request enters through a Server Action or a Route Handler; keep the route file as the request boundary.Use the existing database or payment adapter contract. If it lacks a needed operation, extend the domain boundary deliberately before adding provider code.Keep provider SDK calls inside src/lib/**/adapters/.Add a focused Jest or Testing Library test for the feature rule and an expected failure path.Run the project’s lint, test, and build checks before treating the change as done.This workflow is a recommendation based on the repository’s documented architecture; it is not an assertion that a particular implementation has already been tested for your product.Where src/shared/ helps—and where it hurtssrc/shared/ is useful for UI primitives, layouts, generic hooks, and utilities used by multiple features. It becomes harmful when it is used as a parking lot for feature-specific logic. A “shared” folder full of one-off checkout helpers, dashboard policy checks, and blog-only types obscures ownership and invites cross-feature coupling.Use this decision rule: if deleting a feature would make the module irrelevant, keep it inside that feature. If two independently evolving features need it and the API can stay generic, move it to src/shared/. Keep global configuration, provider setup, and top-level TypeScript declarations in their explicit homes rather than importing them through a generic utility layer.Four failure modes this structure preventsProvider leakage: a React component imports a payment or database SDK, which blurs server/client boundaries and makes later replacement harder.Route logic sprawl: a page file owns validation, authorization, persistence, and presentation, so a second entry point duplicates the rules.Feature-to-feature imports: one feature reaches into another’s private components or data details, making a local refactor unexpectedly risky.Shared-folder gravity: every unsure module is placed in shared, leaving no clear owner and no safe place for agents to make changes.A practical review testBefore merging an AI-assisted change, trace the imports from the changed page or Route Handler. You should be able to explain which feature owns the rule, which server-side boundary validates it, and which adapter reaches the provider. If a component jumps straight to Firestore or Stripe, or a feature imports another feature’s internal component, stop and move the responsibility to its proper boundary. This quick trace catches structural drift before it becomes a costly cleanup.When this structure is the wrong choiceA tiny one-screen prototype does not need every folder shown here. Start smaller if there is no meaningful feature boundary yet. Conversely, this is not an organization-based multi-tenant or enterprise-permissions architecture; those needs require deliberate product and authorization design beyond a folder tree. The structure also does not replace security review, webhook verification, tests, monitoring, or product-specific decisions.For a TypeScript SaaS with authentication, payment state, data access, and a growing dashboard, though, defining these boundaries early is usually cheaper than unpicking them after several AI-assisted feature passes. Start from a codebase whose routes, feature modules, and providers have a clear relationship—then keep the relationship intact as you build.View the reference architectureFrontend Accelerator is a Next.js SaaS foundation that uses the feature-first boundaries described here, along with database, payment, and AI adapters. It is a fit for solo developers and freelancers who want an understandable starting point to extend; it does not replace product-specific engineering or testing. View Accelerator’s architecture.SourcesNext.js documentation: Project structure and organizationNext.js documentation: Server and Client Components

July 13, 2026Learn more

Ready to Launch Your SaaS Faster?

Start from a stable architecture that makes AI more reliable, not confused — so you can go from idea to product in record time.

AI-friendly architecture
Production ready from day one
Lifetime updates