Short answer: build the price from a written scope, add a buffer for the unknowns, and make change requests priced and explicit. Fixed bids fail when scope is vague, not when the arithmetic is wrong.
Step 1: write the scope down
List pages, features, integrations, who supplies content and images, the number of design revisions included, browsers and devices supported, and what is explicitly excluded (copywriting, SEO audits, hosting setup, ongoing maintenance). The exclusions are as important as the inclusions.
Step 2: estimate the work in pieces
Break it into tasks (design, build, content entry, forms, testing, deployment, handover) and estimate each in hours. Include the non-coding time that is easy to forget: calls, feedback rounds, project management and revisions.
Step 3: add a buffer
Add a contingency for risk (commonly 15 to 30 percent, depending on how well you know the client and the stack). Unclear requirements and slow feedback deserve a bigger buffer.
Step 4: turn hours into a price
Multiply by your rate, which must cover non-billable time, tools, tax and holidays, not just a salary. Then sanity check against the value to the client; a site that brings them business can justify more than hours alone.
Step 5: protect yourself in the contract
- Payment in milestones (for example a deposit, a midpoint and a final payment).
- A fixed number of revision rounds, then hourly or per-change pricing.
- A change request process: any new request is written down, priced and approved before work starts.
- A clear definition of "done" and a handover date.
Pitfalls
- Quoting before you have seen the content or the design expectations.
- Not charging for revisions.
- Starting without a deposit.
Track your actual hours against each quote. After a few projects you will know your real error margin.