The trap I fell into was being the "nice" freelancer who wanted to keep clients happy, so I'd say yes to changes and just work extra hours to absorb them. After a few projects like that, I realized I was actually training clients to see scope creep as normal and free. What shifted things for me was treating change requests like an actual business process, not a favor.
Here's what works: build "change request scope" into your contract from day one. When a client asks for something new mid-project, you don't reject it or absorb it - you acknowledge it, quantify the extra hours/cost, and present it as an add-on they can choose to pay for or defer to a future phase. Most clients will drop half their asks once they see a price tag, and the ones who genuinely want the feature will pay for it. You're not being difficult, you're just being clear about what's included vs what costs extra.
The other key thing is getting really specific in your initial scope document. Instead of "responsive design" write "responsive design for phones, tablets, and desktops at these breakpoints" or "up to three rounds of revisions included." When it's in writing, you're not making it up as you go. Also, set a cutoff date - like "scope locked on X date, changes after that trigger the change request process." Clients respect boundaries way more than you'd think, especially if you explain it upfront as how you keep projects on track and profitable. You're protecting the relationship by being transparent, not harming it.