SharePoint Development : SPFx Folder Structure Explained

SPFx (SharePoint Framework) scaffolds a fairly opinionated folder structure via its Yeoman generator — here’s what each part actually does, and the two competing ways to organize your own code inside src/ once a project grows past a single web part.


In this post: The scaffolded structure · src/ and config/ in detail · Type-based vs. feature-based organization · A worked example · Where shared code actually goes · The Heft toolchain changes the root slightly · Best practices · When would you actually use this? · Related reading


The scaffolded structure

What the Yeoman generator produces for a new SPFx project:

my-spfx-project/
├── config/                # Build configuration files (JSON)
├── lib/                   # Transpiled JavaScript (auto-generated)
├── node_modules/          # npm packages
├── src/                   # Your TypeScript source code
│   ├── webparts/          # Web part components
│   └── extensions/        # SPFx extensions (if any)
├── temp/                  # Intermediate build files (auto-generated)
├── sharepoint/            # Deployment assets (.sppkg)
├── teams/                 # Teams app manifest (if Teams support is enabled)
├── package.json
├── tsconfig.json
├── gulpfile.js
└── README.md

src/ and config/ in detail

src/ is where actual development happens — inside src/webparts/<yourWebPartName>/ or src/extensions/<yourExtensionName>/. Each of those typically contains .ts files (the main class and interfaces), a .manifest.json (component configuration), .scss/.css styling, and a loc/ folder for localization resources.

config/ handles build and packaging: serve.json (local dev server config), package-solution.json (solution package settings), and write-manifests.json/build.json/deploy-azure-storage.json for bundling and deployment instructions.


Type-based vs. feature-based organization

The generator’s default layout groups files by type:

/src/
├── components/
├── services/
├── webparts/

Fine for a small project with one or two web parts — harder to trace once a project has several features, since everything belonging to one feature is scattered across multiple type-folders. The alternative groups by feature instead:

/src/features/
├── kudosWebPart/
│   ├── components/
│   ├── services/
│   ├── styles/

Everything for one feature lives together, at the cost of feeling more deeply nested for a small project. The tradeoff flips as a project grows: type-based organization gets harder to navigate exactly when feature-based starts paying off.


Type-based organization gets harder to navigate exactly when feature-based organization starts paying off — the tradeoff flips as a project grows.

A worked example

A “Kudos” web part (posts thank-yous within a site), organized by feature:

/src/features/kudosWebPart/
├── Kudos.tsx
├── IKudosProps.ts
├── KudosService.ts
├── styles/Kudos.module.scss
├── loc/en-us.js

UI, props, service logic, styles, and localization each get a clear, findable home — the whole point of picking a structure deliberately rather than letting files land wherever.


Where shared code actually goes

Neither layout above answers a question that comes up the moment a solution has more than one web part: where does code two or more features genuinely need in common actually live — a shared PnPjs service wrapper, a common TypeScript interface, a reusable button or loading spinner component? Duplicating it into each feature folder defeats the point of organizing by feature in the first place, and dropping it into whichever feature happened to need it first just hides it from everyone else. The common answer is a dedicated top-level folder, sitting alongside features/ rather than inside any one of them:

/src/
├── features/
│   ├── kudosWebPart/
│   └── announcementsWebPart/
├── shared/
│   ├── services/       # e.g. a shared PnPjs wrapper both web parts call
│   ├── components/     # genuinely reusable UI (buttons, spinners, error boundaries)
│   ├── models/          # shared TypeScript interfaces/types
│   └── utils/           # formatting helpers, constants, etc.

The discipline that actually makes this work is restraint: something only belongs in shared/ once at least two features genuinely use it, not preemptively because it might be reused someday. Moving a component into shared/ the first time a second feature actually needs it is a five-minute refactor; guessing wrong up front and over-abstracting a single-use component into a “shared” one just adds indirection nothing else benefits from.


The Heft toolchain changes the root slightly

Worth knowing before assuming the scaffolded structure at the top of this post is universal: it reflects the gulp-based toolchain, which SPFx 1.22 replaced with Heft, Microsoft’s own task orchestrator. A project scaffolded on 1.22 or later has a `config/heft.json` file alongside (or instead of) `gulpfile.js`, and the build commands change with it — `heft start` instead of `gulp serve`, `heft build` instead of `gulp build`, `heft build –production` instead of `gulp bundle –ship`. The `src/` layout and the type-vs-feature organization question this post covers are unaffected either way — Heft changes how the project builds, not how source code underneath `src/` should be organized. Worth checking which toolchain a given project actually uses before following build instructions from an older guide, since a `gulpfile.js`-based command won’t exist at all in a pure-Heft project.


Best practices
  • Keep components small and modular — avoid building an entire web part in one file.
  • Switch to feature-based grouping once type-based gets hard to navigate — there’s no fixed threshold, but “I can’t find all the files for X anymore” is the signal.
  • Keep secrets and config out of version control — a deliberate .gitignore, not an afterthought.
  • Externalize strings to loc/ from the start if there’s any chance the tenant needs multilingual support later — retrofitting localization after the fact is far more tedious than building it in.
  • Write a real README — the next person touching this project (including future you) shouldn’t have to reverse-engineer the structure.

When would you actually use this?
  • You’re scaffolding a new SPFx project and want to understand what each generated folder is actually for before you start adding files.
  • A project has grown past 2-3 web parts and the default type-based layout is making it hard to find everything related to one feature — time to consider the feature-based restructure.
  • You’re onboarding a new developer and want a structure that’s self-explanatory rather than requiring a walkthrough.

References: SPFx documentation, PnP samples and guidance, SPFx Yeoman generator, SPFx extensions overview.



That’s the structure end to end. Questions about organizing a specific project? Comment below.


App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning

Leave a Comment

Your email address will not be published. Required fields are marked *