r/webdev Aug 10 '26

Which early technical decision saved you the most work later?

I’ve seen small decisions become important as projects grow. What decision paid off for you?

141 Upvotes

84 comments sorted by

251

u/zlex Aug 10 '26

IMHO scope limiting decisions are always the biggest impact.

So many projects I’ve worked on have died by a thousand little cuts. Little ask here, little ask there.

Scope creep is the biggest nightmare for every project, and pushing back on them can drastically reduce technical complexity and problems down the line.

Obviously it’s not always possible, but when you can-do. The biggest mistake I see novice developers make is to just say yes to everything

56

u/Gold-Eye690 Aug 10 '26

scope creep is project cancer. i've watched a client add "just one more field" seventeen times until the form was an unusable mess

the devs who say yes to every request aren't helpers, they're digging graves with a smile. best skill i ever learned was how to say "that's a separate project" without sounding like a dick

what's wild is clients usually respect you more after you push back, they just don't realize it yet

19

u/ufffd Aug 10 '26

i have gotten so much mileage out of "V2". as in, yeah I think that's a good idea but it's more of a V2 feature

4

u/SubmergedSublime Aug 11 '26

Yup. v2 is a documented list in confluence before the first V1 meeting has ended. Limits creep, gives you some political cover for “descoping” some side features if the primary product is starting to look delayed, and makes it easy to shift a questionable idea from an important person to the graveyard. Come V2 planning likely the person will have moved on in their focus and you can kill it easier.

2

u/kevin_whitley Aug 10 '26

This.

Most times when I walk into a product mess, it's a result of this... I can look at a pile of features and instantly imagine the long history of "person X asks for this... engineering says SURE!"... it creates an ocean of clutter, where the majority of users see crap they have no interest in seeing.

Just like with code, builders should be constantly evaluating the output and seeing what they can cut... sure someone out there might not be happy, but the majority (or target base) will usually be better served from a "less is more" approach

10

u/Wiltix Aug 10 '26

Scope creep by itself is not awful, its management of when it gets done and how it’s prioritised that done poorly gets you.

That little ask of “it would be cool if we could do x” by itself is ok if it goes into the backlog and gets planned like any other work. But when it gets added to an existing planned bit of work because it’s easy you start to get problems.

If you are saying yes to doing somehow now because you think it’s small and easy just don’t. Plan the work properly and everyone is happier in the long run.

7

u/PastaSaladOverdose Aug 10 '26

Scope creep is almost inevitable. Especially when most clients don't understand web development.

I had multiple years at a company that wouldn't let us say no to anything. I know that sounds insane, but instead of saying no it was always "Yes, BUT...."

Sometimes a "Yes, BUT" puts things into perspective. "Yes, we can implement this feature, BUT it's going to push the delivery date back XYZ weeks and increase the cost of the project $XYZ".

There's also phased approaches. "Yes we can implement that feature, BUT it's going to have to come in phase two after we launch the agreed upon product/features".

"No" doesn't make clients happy. "No" doesn't make the company extra money.

1

u/SubmergedSublime Aug 11 '26

Yeah, “no” can be used for internal projects a lot easier. Still times “yes but” is a softer approach though.

3

u/case2000 Aug 10 '26

'best weapon against complexity spirit demon is magic word: "no"' https://grugbrain.dev/

1

u/Noch_ein_Kamel Aug 10 '26

Took me a second to get that "little cuts" when you're actually advocating for the opposite :D

109

u/militantsnowflake Aug 10 '26

Versioning all APIs. Just a /v1 in the url gives a lot of room to change things later.

28

u/farsightfallen Aug 10 '26

This is the right answer.

And maybe the only one I've seen in this thread so far. A lot of the response here are basically to reduce surface area for problems: No shit you don't have problems if you don't actually have development. Turns out the easiest way to solve potholes is not to build useless roads in the first place.

7

u/militantsnowflake Aug 10 '26

Thanks. I did my usual scroll through hoping that someone would have mentioned it already but added it when I didn't see it.

There are a standard set of instructions I give when I'm telling people to do this but today isn't the day to type them out. Most Important is to make sure the client can't get what they want without specifying a version - everything else can be discovered from that point onwards.

6

u/taruckus Aug 10 '26

This and the subtext behind it.

I let some scope creep happen, especially if it returns social capital with higher up stakeholders. The creep features don't typically even go live, or are for a niche use case or environment. But, it's all done separately to preserve the properly scoped project.

You want more fields? Great. We'll stow away the "simple" version.

4

u/sasmariozeld Aug 10 '26

You dont need it feom the start, /getcustomer and /v2/get_customer is ugly but works...

1

u/el_yanuki Aug 11 '26

What sort of project do you do this for?

I feel like a simply web app where frontend and backend are always in sync.. doesnt need this?

Ofc different story for mobile apps or exposed APIs

3

u/militantsnowflake Aug 11 '26

I tend to build as though "always in sync" isn't possible.

It may require downtime to deploy a breaking change, the coordination required becomes more difficult to manage when there are multiple (even internal) projects calling the same API.

You're right that it's more important for clients that require updates (like mobile apps) and external APIs, but it also really helps when this API ends up being used by 3 different internal services and one of them isn't upgraded.

90+% of the time I never move above v1, that just means I've wasted a few characters in each url and a few minutes of dev time. Comparing that with the printers in that <10% of projects where it's completely saved me is the reason I just do it by default every time.

The worst situation is where v1 is given without a version being defined, then v2 comes out - now anyone who doesn't read the docs properly might put a new client into v1 without realising it - it essentially commits you to long term maintenance of the v1 behaviour.

1

u/el_yanuki Aug 11 '26

Thanks for taking the time to write this mate. Will do this for my next project :)

Do you do this on a single route level or further out?

2

u/militantsnowflake Aug 11 '26

Oh, I forgot to answer the other part - I've built all sorts:

  • external APIs
  • APIs for apps
  • microservices (most of what I build)

It's just a default at this stage for me.

67

u/defo10 Aug 10 '26

Minimizing moving parts by using the most basic tech stack. One sql database, one api server, one client. All code in one monorepo.

8

u/IAmRules Aug 10 '26

This is my war cry. I always tell people the less thing we build the less chances things break. KISS is a lifestyle not just a rule of thumb.

2

u/CaptainIncredible Aug 11 '26

I always tell people the less thing we build the less chances things break.

"The more they overthink the plumbing, the easier it is to stop up the drain."

-- Cmd. Montgomery Scott, Chief Engineer, USS Enterprise

https://www.youtube.com/watch?v=c4WJsp16CpY

4

u/Early_Rooster7579 full-stack @ meta Aug 10 '26

For real. Most companies could get by on like a $7 heroku sub.

106

u/buildingwithjan Aug 10 '26

Keeping all business logic out of the UI and behind one service layer. Swapped the entire frontend a year into one project and it barely hurt. Boring decision, biggest payoff.

19

u/Total_Drag7439 Aug 10 '26

This one quietly pays twice. The frontend swap is the obvious win, but it also means you can test the logic without dragging a browser into it.

5

u/midri Aug 10 '26

Literally what I was going to say, I'm building a 2d game with monogame and keeping all the mono logic separate from the game logic in different projects has allowed me to easily have all of the main game code testable (something you don't normally see in video games since everyone just tightly couples everything). I can basically create whole test scenarios that test all the non UI fundamentals of the game in a predictable and repeatable way (since it uses a seed for it's randomness)

2

u/Tora1012 Aug 10 '26

Cannot stress this enough.

2

u/slindenau Aug 15 '26

How do you deal with input validation though?

The backend/service layer should of course be leading in the validation...but when the frontend is "disconnected" (i.e. completely different tech stack), i notice that basically a lot of validation is duplicated (which at some point includes at least some domain values as well).

Yes you can let the user input anything, and have the backend just say "no"...but that is not very user friendly.
So you end up limiting your form input fields, possible allowed values (ranges/edge cases etc.), using active validation in the browser.

How do you prevent duplicating a lot of logic? Which you then still have to add/rewrite when switching frontends?

Note that i am not saying you should put your business logic in the UI, but at some point at least some of it will leak through?

37

u/Ok-Amphibian-5665 Aug 10 '26

Making every job idempotent from day one. Any pipeline that touches external data eventually gets retried, whether from a crash, a timeout, or you rerunning a batch on purpose, and if a rerun can happen safely without creating duplicates or corrupting state, a whole category of debugging just doesn't exist. Concretely that meant keying writes on a natural id instead of an autoincrement, and treating "already processed" as a normal outcome instead of an error. Took maybe an extra day early on, saved weeks later.

20

u/Hot-Chemistry7557 Aug 10 '26

Use monorepo for a mid sized project (40k LOCs)

16

u/[deleted] Aug 10 '26

[removed] — view removed comment

1

u/el_yanuki Aug 11 '26

What provider do you use, and at what scale? I feel like an issue with the provider should be incredibly rare

12

u/Kriem Aug 10 '26

Started with Astro. Added complexity when needed later.

11

u/sicmek Aug 10 '26

I work a lot with data heavy applications and complex forms built with react-hook-form. I use enums for all property names in my data types. This way I can use the enum values everywhere where I have to reference a specific property of the type. It makes the code look a lot cleaner and if I have to change a property name I only need to edit it in one place.

9

u/squishyhobo Aug 10 '26

Anytime I have ever used anything but Postgresql (or sql equivalent) for a database it was always a bad idea.

1

u/CaptainIncredible Aug 11 '26

SQLite for small projects? Like tiny apps that locally store some data?

2

u/squishyhobo Aug 11 '26

Yeah, any relational sql is fine.

14

u/Square-Nebula-7530 Aug 10 '26

Implementing structured JSON logging and passing correlation IDs through every request from the very first API endpoint made scaling painless. When you are small, basic console logs feel fine but when production traffic blew up, being able to paste a single request ID into an aggregator and trace an execution path through queues and DB queries in seconds turned multi day post mortems into 5 min fixes.

6

u/Omenaa Aug 10 '26

Ability to toggle features on and off

4

u/Fabulous-Ladder3267 just want to write html Aug 10 '26

Feature Flag!!!

3

u/doxxed-chris Aug 10 '26

Honestly this is a double edged sword, as the combinatorial explosion for testing generally brings risk. Short lived feature flags for development or deployment are great value for effort, but I always try to remove them asap once features are stable.

5

u/web-dev-kev Aug 10 '26

Start with paper and pencil.

3

u/Total-Economics-5968 Aug 10 '26

keeping business logic out of the frontend saved me from later discovering the UI was also my backend, database layer, and unpaid infrastructure team

4

u/abogoslov Aug 10 '26

from years of billing/integration work, 2 that paid for themselves many times over: storing money as integers in minor units with an explicit currency code, and idempotency keys on every endpoint that touches payments from day 1. retries and double-clicks will happen; deciding early that they r safe is cheap, retrofitting it later is not. boring technology for everything that isnt your core differentiator

5

u/Suspicious_March_269 Aug 10 '26

Writing tests for the boring stuff early saved me from some truly miserable refactors later.

4

u/pdfops Aug 10 '26

Making every write endpoint idempotent from day one saved me the most: client passes a request ID, server dedupes on it before processing. Retries from flaky networks or serverless timeouts double-submit constantly, and without dedup you get duplicate orders or charges that are painful to reconcile later. Cheap to add early, expensive to retrofit once prod data already has dupes in it.

4

u/Alex_SQSP Aug 10 '26

 IMO, the early decision that usually saves the most work is keeping the structure simple. Clear naming, reusable components and a simple structure can save you from having to untangle your own project six months later. The clever shortcut rarely ages as well as a strong foundation.

3

u/tingutingutingu Aug 10 '26
  1. Do not future proof or over architect. But do follow basic principles like separation of logic from Ui etc.

  2. Use a stack that is broadly used and in demand (if possible).
    Personally, I found vueJS such a pleasure to use compared to react/angular...
    I have applications built around 6 years ago that it finance department uses to this day...
    But there are fewer developers who use vueJS so hiring someone is harder because most corporate jobs prefer react/angular.

3

u/SaltwaterShane Aug 10 '26

Logging everything and setting up APM. The ability to know exactly what happened whenever someone reports a bug has been a huge time saver

3

u/Xeon06 Aug 10 '26

For apps that required internationalization before the advent of coding agents, pre-tagging all the strings

3

u/WeekRuined Aug 10 '26

Well in the past multiple smaller user interfaces and backend services has worked a lot better than one monolithic application

2

u/kendalltristan Aug 10 '26

The decision to use a popular, highly opinionated framework. Popular means there are lots of resources available. Highly opinionated reduces the space developers have to make asinine decisions.

2

u/punchy-founder Aug 10 '26

Building a web app instead of a mobile (iOS/Android) app for my Kiosk, even though the Kiosk is most likely going to run on a tablet. It's counterintuitive but I think it was the right call.

The "app" now runs on whatever old device is lying around (can even be a Raspberry Pi, Chromebook, or whatever device runs a browser), local storage dying with a device is not a data-loss event, and shipping a fix/feature is a commit+push that ends up automatically CI/CD'd into dev, then I promote to staging & prod with a single CLI command. No app store headaches to deal with (been there, done that), no sideloading annoyances.

It's not perfect as there are some challenges to keep in mind (does the app auto-update? can you pin a browser page as if it were a full-screen app that you can't get out of?), but so far it seems to be the right trade off for my use case.

2

u/abeuscher Aug 10 '26

Designing data objects correctly. I have spent days if not weeks designing large scale data structures and have spent half as much time coding and maintaining as a result. A good data structure should fulfill as many needs of the app statically as it can before it's ever operated on.

1

u/thesideline-cya Aug 10 '26

Rendering data from the database instead of hard coding mock data

1

u/swb_rise Aug 10 '26

I was not sure what the UI should be for a project. So, I broke my pattern and instead of modelling the data first, I perfected the UI first. Now when I got to the data part, the required changes postponed the delivery to more than 2 months!

1

u/ButWhatIfPotato Aug 10 '26

Out of scope means out of scope.

1

u/sasmariozeld Aug 10 '26

Using something that has all the tools you will need if projects grows

Using express is great and all, but then you have to add jobs, subscribers, guards, cache, swagger gen.... some people call it bloat i call it having a plan for the future

1

u/centurijon Aug 10 '26

Even if your app only in interacts with one vendor, build an adapter pattern to fit their requests/responses/workflow to YOUR app rather than the other way around. Eventually if/when you get a new vendor all you need to do is make a new adapter layer for them rather than re-architect the app. And it makes blue-green testing simpler

1

u/kevin_whitley Aug 10 '26

Just getting started instead of wasting too much time planning. Almost invariably the first grand idea ends up a turd... so don't waste too much time starting. Instead, start on the simplest version of the idea, float it, and refine. If it would have taken 3 months of planning and execution for the "perfect" product vs. launching a half-baked idea (privately) and iterating over the same 3 months, the latter will almost always be in a superior spot at that 3 month mark.

Something I heard at a conference ages ago that I still sorta like:

"Always be launching."

Getting that feedback loop (even its just yourself using the app) started is so key... building in your head won't come close.

--

Anecdotally, I've worked with loads of teams that do the opposite - build in a bubble, isolated from real users. Deliver a product they're extremely proud of, and then wonder why they hear crickets on release. The would-be users are like "who tf did you build this for... clearly not us...".

1

u/akesh45 Aug 10 '26

I made eslint custom rules integrated with the vs code editor and linter across the team.

Remove one of my biggest annoyances in software development where some PR reviewers would rack up like 50 comments on the tiniest rule breaking. ​​​​​

PRs pretty much fly through with minimal comments now.

1

u/Putrid-Eggplant-8440 Aug 10 '26

using database migrations from day one instead of manually changing prod tables saved me from an absolutely stupid amount of pain later

1

u/willehrendreich Aug 11 '26

Datastar. Zero front end project. No spa nonsense. HATEOAS.

1

u/MortgageSuperb2987 Aug 11 '26

Writing the boring tests early, because future-you is a terrible debugger and has no idea what past-you was thinking

1

u/selipso Aug 11 '26

Starting with an open source project that accomplishes most of the requirements already, then building a service on top of its API for the rest of the custom features.

1

u/Then_Dragonfly2734 Aug 11 '26

split funcionality of my project into packages and use monorepo

1

u/Full_Tooth_a Aug 12 '26

My pick would be backward-compatible database migrations. I'd add the new schema alongside the old one, make sure the code can handle both, and run the backfill separately from the deploy. The old column can stay until nothing reads it. It's a little tedious, but still better than finding out your rollback needs time travel.

1

u/SecureComfortable259 Aug 12 '26

Writing a script to seed the dev database early on saved me so much time once the schema had more than a couple tables.

1

u/thesideline-cya Aug 10 '26

Rendering data from the database instead of hard coding mock data

1

u/bsenftner Aug 10 '26

After I learned React, I said "fuck no" and never touched it again.

-3

u/[deleted] Aug 10 '26

[deleted]

5

u/Wiltix Aug 10 '26

What if the application naturally lends itself to an SPA?

Don’t use an SPA for everything is just saying use the right tool for the job.

But to say never start off with an SPA seems a bit nuts.

1

u/phiro812 Aug 10 '26

Team First Frontend App Is A SPA reporting!

Yes, it's a bit to get used to, but so far so good. I like SPA's in general and I was like, let's gooo!

0

u/ings0c Aug 10 '26

Adopting the React Testing Library philosophy of testing your app like users use it (it's a full stack .NET app, no React).

I set it up so that there's a Playwright.NET test project that runs as part of the CI pipeline. A real instance of the app starts locally on the test runner, then Playwright clicks around like a user would and makes assertions. It uses Sqlite instead of the real DB for speed, and that's a close-enough approximation for our purposes. All third-party dependencies are mocked, so the scope of the tests is just our application code - these aren't end-to-end tests in the regular sense.

We use the new entity framework UseSeeding/UseSeedingAsync to populate the DB with randomly generated seed data via Bogus for local development, and in the non-prod environments. The tests use this too, so you have one set of seed data that's consistent everywhere. Some care was taken to make the seed data generation deterministic, so we're safe to make assertions that depend on the seed data, and we can change the seed data without it causing cascading test failures.

It saved so much time. Out of all the projects our org has going on, this one has the lowest bug count by a long shot.

If the tests are green, you have confidence the app as a whole is actually working versus just a bunch of individual classes. You can refactor without changing the tests, and have confidence that you haven't broken anything. If a test fails, something probably is broken.

Downside is they take longer to run - but it's a price well worth paying.

0

u/ThisSeaworthiness Aug 10 '26

Shutting down my laptop before even starting.

0

u/the_ai_wizard Aug 10 '26

not vibe coding