Project Structure
A useful project structure lets a developer locate a feature, change it, test it, and deploy it without guessing which files own which behavior. Organize a project for its current requirements, then refactor when responsibilities become unclear. There is no universally correct folder tree, and renaming folders alone does not improve runtime speed, security, or scalability.
What to optimize for
Walk through an actual change before picking folders
Suppose a product owner asks for an email subscription form. Trace one task end to end: where does the HTML form live? Which stylesheet owns spacing and states? Where does client-side validation run? What endpoint receives the POST? Which tests cover invalid input and server rejection? If answering requires searching a dozen unrelated folders, the structure may need to change; if everything is in a five-file project, splitting into 20 directories makes it worse.
A useful change trace:
User requirement
-> HTML form and labels
-> CSS state styles
-> JavaScript event handler
-> /api/subscribe contract (server, not public bundle)
-> unit + integration + browser tests
-> preview deployment -> production
The actual browser form example illustrates the user-visible end of that chain. The project source keeps markup, CSS and JavaScript small enough to inspect. It intentionally does not implement /api/subscribe, so it is not a production email service.
Decision check: when changing the label, should you edit the HTML or five duplicated component definitions? When changing validation, can you test it without launching the entire application? When changing the endpoint, can you preserve the frontend contract? Document boundaries in the README. Do not mistake folder count for architecture quality.
- Findability: use consistent names so a new contributor can locate an entry point, component, stylesheet and test.
- Maintainability: keep related behavior together, isolate clear boundaries, and document unusual decisions.
- Reusability: extract common components when multiple consumers share genuine behavior; premature abstractions can make simple changes harder.
- Modularity: limit unrelated dependencies so a change can be understood and tested locally. A folder does not create isolation unless the code respects its boundaries.
- Collaboration: define ownership, contracts and review expectations. Separate directories may reduce conflicting edits, but do not guarantee conflict-free teamwork.
- Performance: measure actual assets, bundle size, loading behavior, caching and render work. A tidy file tree alone does not reduce bytes shipped to users.
- Security: put secrets in a secure server or hosting configuration, validate and authorize operations server-side, and restrict access in the deployed architecture. A
private/directory in browser code does not make its contents private.
Example: moving an API key from src/key.js to src/config/secret.js changes its location but not its exposure when both are bundled for the browser. The fix is to move secret-dependent operations to an authenticated backend or an appropriate server-side function.
Choosing technologies
HTML provides structure, CSS presentation, and JavaScript/TypeScript behavior. A plain HTML/CSS/JS site can be a good fit when the interaction is small; a framework may help with complex component composition, state or routing, but adds conventions and build dependencies. TypeScript adds static checking when configured, not automatic runtime validation or performance.
Choose based on real requirements: deployment model, accessibility support, team experience, ecosystem maintenance, testing, bundle budget and integration needs. Multiple frameworks in one application can be appropriate during migration or for isolated islands, but need deliberate integration boundaries; avoid combining them without a reason. Do not claim that plain JavaScript always outperforms every framework or that choosing an actively maintained dependency guarantees security.
Example structure: a small multi-page site
The original CSS/JS/views/assets split is still a useful example. Here is a streamlined version that makes entry points and test ownership explicit:
project/
βββ index.html
βββ about.html
βββ contact.html
βββ css/
β βββ reset.css
β βββ typography.css
β βββ layout.css
β βββ navigation.css
β βββ forms.css
β βββ themes/
β βββ dark.css
β βββ light.css
βββ js/
β βββ main.js
β βββ utils.js
β βββ components/
β β βββ header.js
β β βββ sidebar.js
β βββ services/
β βββ api.js
βββ assets/
β βββ images/
β βββ fonts/
β βββ videos/
βββ data/
β βββ sample-data.json
βββ tests/
β βββ navigation.test.js
βββ docs/
β βββ decisions.md
βββ README.md
- HTML entry points:
index.html,about.html, andcontact.htmlare separate pages. A template engine could instead compile sharedviews/partials/into these files; an HTML file does not automatically include another HTML file merely because they live in apartialsfolder. - CSS: split reset, typography, layout, navigation, forms and themes only when each file has a clear purpose. Media queries can sit beside the component they adapt rather than in one large
media_queries.css. - JavaScript:
main.jscoordinates page startup,components/contains UI behavior,services/handles API boundaries, andutils.jsshould not become a dumping ground. - Assets: organize images, fonts and videos for maintenance; optimize formats and delivery separately. Images need suitable dimensions and alternatives where meaningful.
- Data and tests: keep sample fixtures separate from production secrets, and test observable behavior rather than implementation details.
- Documentation: root
README.mddescribes installation, scripts and examples;docs/can hold architecture decisions. A document nested indocs/does not replace an obvious root entry point by itself.
This is a suggestion, not a mandatory architecture. For a five-file learning example, five files in one directory may be easier to understand. A bundled app may have src/, public/, dist/, a lockfile, or a generated output directory instead; do not commit generated output unless deployment requires it.
Alternative: organize by feature
Worked feature slice: cart with public and private boundaries
A feature-oriented tree is useful only when it reflects real ownership, not arbitrary directories:
src/
features/
cart/
CartButton.jsx
cart.css
cart.test.jsx
cart-api.js
shared/
Button.jsx
format-currency.js
server/
routes/
checkout.js
services/
payments.js
cart-api.js may send a request, but it must not contain a private payment-provider token: everything shipped in a client bundle can be inspected. The server checkout route validates prices and quantities against authoritative data and checks the user's permissions. The client can display a calculated subtotal for feedback, but the server must recompute amounts before charging. Tests at each boundary check different risks: rendering and keyboard focus, request contract, business rules, and the full checkout workflow.
For smaller sites, keep related files together without introducing an index.js re-export for every folder. A barrel file that imports unrelated modules may complicate dependency graphs; measure bundle output before claiming it makes tree shaking faster. Move a module to shared/ only when actual consumers share stable behavior. Avoid circular imports: extract common interfaces or rethink ownership when A imports B and B imports A.
Exercise: on paper, implement a βRemove from cartβ action: mark the UI module, state owner, API boundary and test. Then add an inaccessible alert as a simulated error and decide how to give a meaningful recovery message. An attractive folder diagram is not evidence that the feature works.
As a component application grows, separating everything by file type can scatter one feature across multiple directories. Compare these simplified arrangements:
# Type-first # Feature-first
src/components/CartButton.jsx src/features/cart/CartButton.jsx
src/components/CheckoutForm.jsx src/features/checkout/CheckoutForm.jsx
src/styles/cart.css src/features/cart/cart.css
src/styles/checkout.css src/features/checkout/checkout.css
src/tests/cart.test.js src/features/cart/cart.test.js
Actual browser-rendered before/after:

A feature-first arrangement can simplify locating its tests and styles. Shared primitives (button, dialog, formatting functions) can still live in src/shared/. Use import boundaries to prevent cycles rather than expecting directory names to enforce architecture. The illustration teaches interface hierarchy, not a benchmark proving one file tree is faster.
Best practices for a production build
Deployment artifacts and environment variables are separate concerns
source files (src/)
| lint, type-check, tests
v
build step -> output files (dist/)
| copy/publish only expected artifacts
v
hosting + HTTPS + routing + caching
A bundler may inline environment variables into JavaScript. Anything in the browser bundle should be treated as public, even if named SECRET_KEY or kept in a hidden folder. Server secrets belong in protected deployment configuration, and the server must authorize requests independently of frontend UI visibility.
A typical package.json might have explicit scripts for lint, test, build, and preview; their exact commands depend on project tools. The deployment host must publish the correct output directory and handle deep links appropriately. Do not upload node_modules/, .env secrets or local credential files merely because they exist in a repository checkout.
Verification checklist: inspect generated HTML and asset URLs; check route reloads on preview; view the browser Network tab for missing fonts/images; measure JS and CSS transfer size; check console errors; try keyboard and narrow viewports; verify rollback instructions. Compare raw versus styled card output to ensure the correct stylesheet reached production. Reference: OWASP secrets management.
- Bundle, split and minify assets when measurements show a benefit; modern project tooling often handles this automatically. Webpack, Parcel or another bundler is optional for small static examples. Gulp is a task runner, not a mandatory bundler.
- Use a template engine such as EJS, Handlebars or Pug for repeated server/build-time HTML only when duplication warrants it. Check the output HTML after compilation.
- Separate development, preview and production configuration. Never place server credentials in client-exposed environment variables.
- Use Git, a
.gitignore, documented scripts and meaningful commits. Commit a lockfile where appropriate for reproducible dependency installation. - Run a formatter and relevant linting tools (such as ESLint or Stylelint) consistently; tools catch selected problems, not all bugs.
- Write down decisions that future contributors cannot infer from code alone. Update the README when commands, environment variables or ownership change.
Boilerplates
Template evaluation worksheet (before cloning)
| Question | Evidence to collect | Failure signal |
|---|---|---|
| Is it maintained? | Release history, current dependencies, issue activity. | Unsupported runtime or abandoned dependencies. |
| Can it build? | Documented setup and a clean install/build. | Undocumented manual edits. |
| Is it accessible? | Keyboard/focus/form tests on example pages. | Placeholder-only labels and hidden focus. |
| Is it appropriately licensed? | Repository license and asset licenses. | No grant for your intended use. |
| Is it deployable? | Output directory, routing, environment contract. | Works only on the author's machine. |
| Can we remove unused parts? | Dependency graph and generated output. | Unnecessary services or bundled secrets. |
Do not turn a tutorial's git clone example into an unverified recommendation. If a template teaches deprecated APIs, show the specific migration (such as moving from ReactDOM.render to createRoot) and keep its historical context. Record which upstream revision you actually inspected; labels like βlatest best practicesβ expire quickly.
Hands-on task: run the repository's minimal visual examples without any install step, then inspect a chosen framework template's setup. List which extra behaviors (routing, server rendering, testing, linting) justify each new dependency. Reference: React app setup.
A boilerplate is a starting template containing some combination of files, dependencies, configurations, styles and example code. Its purpose is to remove repetitive setup, not to guarantee that every generated application is accessible, secure or fast. Review the generated project; delete unused example routes, dependencies and placeholder secrets before shipping it.
Why use one?
- Share a conventional entry point and scripts across a team.
- Reuse appropriate lint, formatting, test and build settings.
- Avoid repeating setup when building several projects with the same constraints.
- Make onboarding easier if the template is documented and maintained.
The drawbacks are important: a template can lock you to unsupported dependencies, introduce unnecessary features, or conceal how the tooling works. Check source, license, update frequency, and support for the target framework and Node.js version.
CSS in a boilerplate
Reusable base styles, typography, spacing tokens, and accessible form/button states can provide consistency. Responsive behavior is not guaranteed just because a template contains a media query; test narrow widths, long labels, 200% zoom, and keyboard focus. Naming methods such as BEM or OOCSS are options, not requirements for all projects.
See what such a decision looks like: the repository's visual teaching project has a shared stylesheet and minimal JavaScript. Inspect how its class names map to specific demonstrations and compare unstyled versus styled cards. Change its spacing and color tokens in styles.css to see why predictable naming helps maintainability.
Finding and evaluating a boilerplate
Search the upstream project's real organization and documentation instead of copying placeholder URLs such as https://github.com/username/boilerplate-html-css-js; that address was an illustrative placeholder in the previous notes, not a recommended repository. Read the README, license, dependency versions, issue activity, and setup instructions. Test accessibility, performance and production output with your application, not only the template's screenshot.
Important dated guidance: Create React App has been deprecated for new applications; don't recommend it as the default current React scaffold. React's documentation describes supported framework choices and building from scratch. Vue CLI is in maintenance mode; consult Vue's current tooling guide. Framework and CLI recommendations should link to current documentation and mention the version they were checked against.
Using HTML5 Boilerplate step by step
The following workflow uses an actual template repository, HTML5 Boilerplate. Check its current prerequisites and license before running these commands:
git clone https://github.com/h5bp/html5-boilerplate.git my-new-project
cd my-new-project
Inspect README.md, package.json, and the existing scripts before installing dependencies. If the current version uses npm, install dependencies using the repository's documented command (for example, npm install, or npm ci when a compatible lockfile is present):
npm install # only if supported by the template's current instructions
Customize the project title and description, fonts, CSS, markup and any interactions. Remove placeholder analytics identifiers, unused assets and example content. Make sure the generated page has meaningful headings and labels; a default appearance is not evidence of good accessibility.
If you intend to make an independent repository rather than contribute back to the template's history, the original git clone already created a repository with its own Git history. Running git init alone does not remove that history. Choose deliberately between preserving upstream history, using a template's βUse this templateβ action, or starting a fresh repository with the template files (and honoring its license):
# One possible fresh-history workflow after copying the permitted template files
# into a separately created directory (not inside the original clone):
git init
git add .
git commit -m "Start project from documented template"
Write a project-specific README.md covering purpose, prerequisites, installation, scripts, environment variables (names only, not secret values), testing and deployment. Run the build locally; test links, mobile layout and keyboard behavior before deploying.
Reference directory: review maintenance before adopting
Some links below were part of the original chapter and are retained for historical context. An older generator is not automatically a recommended choice today; check the linked project's current support status and migration path.
- Rapid Site Development
- A Minimal HTML Document
- Reset Revisited by Eric Meyer
- CSS Reset
- HTML5 Boilerplate
- Node.js Boilerplate
- Express Generator
- Create React App β deprecated for new apps
- Vue CLI β maintenance mode
- Angular CLI
- Next.js
- Nuxt
- Meteor
- Webpack
- Parcel
- Gulp
- Yeoman
- Preact
- Sapper β historical; see SvelteKit
- Quasar Framework
- Aurelia
- Gridsome
References: React: start a new React project, React: sunsetting Create React App, Vue tooling, MDN: deployment, OWASP cheat sheets.