Rolling out a Content-Security-Policy without breakage
Turning on a Content-Security-Policy in enforcing mode on the first try is how checkout pages end up blank on a Friday. The header has a mode built for the alternative.
Report first
Content-Security-Policy-Report-Only uses the same syntax but blocks nothing. The browser sends a JSON report for every violation instead. Send a deliberately strict policy in report-only mode and read the reports for a week or two. You will find scripts nobody remembered: a tag manager loading another tag manager, an A/B tool, an old chat widget.
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-r4nd0m';
object-src 'none';
base-uri 'none';
report-to cspExpect noise. Browser extensions inject scripts and trigger reports, so filter by source-file and look for patterns instead of chasing every line.
Nonces or hashes
An inline script is allowed if it carries a nonce that matches the header, or if its content hash is listed. Nonces suit server-rendered pages: generate a fresh, unpredictable value per response and put it on each <script>. A nonce that repeats between responses gives no protection. Hashes suit static pages, because the content never changes.
'strict-dynamic' is worth a look. It lets a trusted script (one with a valid nonce) load further scripts, which is how tag managers usually work, without listing every host.

Inline styles are the smaller risk
Blocking inline scripts removes the most dangerous injection vector. style-src 'unsafe-inline' is a weaker hole, since injected CSS can leak data or restyle the page but cannot run code by itself. Many teams accept it, at first, in exchange for a policy they can actually deploy. Removing inline style attributes entirely is a later, separate project.
Finish with the parts that cost nothing: object-src 'none', base-uri 'none' (or 'self'), and frame-ancestors in place of the old X-Frame-Options.
report-to is replacing report-uri, but keep both for now
The report-uri directive, which points violation reports at a URL directly, is deprecated in favour of report-to, which names a reporting group defined by a separate Reporting-Endpoints (or the older Report-To) header. The newer mechanism is part of the general Reporting API and can also carry other kinds of reports, such as deprecation warnings, through the same pipeline. Because browser support for the newer header rolled out gradually, most real deployments send both directives for a while, with report-uri as the fallback for anything that has not caught up.
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-r4nd0m';
report-to csp;
report-uri /csp-reportsOnce the enforced policy is stable, require-trusted-types-for 'script' is worth a look as a follow-up project. It blocks the DOM sinks (innerHTML and similar) that most DOM-based XSS goes through, by requiring that any string assigned to them first pass through a Trusted Types policy your code defines. It needs real code changes in most existing applications, so it belongs after the CSP rollout, not inside it.