Creating the Perfect Privacy Policy & Cookie Consent Banner for Solo Developers (Jekyll & GDPR Ready)
When running a personal or small toy project, we often overlook the ‘Privacy Policy’. However, the moment we add ads or analytics tools to our site, we begin processing data from users worldwide. This makes us subject not only to local privacy laws but also to stringent regulations like Europe’s GDPR and California’s CCPA.
It might seem complicated, but don’t worry. This article provides a complete A-to-Z guide on how to build a privacy policy and a cookie consent banner system for a static site like Jekyll, helping you meet legal requirements and earn user trust.
Step 1: Building the Policy Framework – What Needs to Be Disclosed?
The first step is to create a document that transparently explains what information our site processes, why, and how. After much discussion, we finalized a policy in Korean, English, and Chinese that includes the following essential clauses.
Required Clauses
- General Provisions: Clearly states who the policy applies to (the site operator). Using a real name might feel awkward, but it’s a legal obligation to identify the data controller and a first step in building trust.
- Items and Purposes of Collection: Explains what information is collected and for what purpose. In my case, I minimized data collection to only what’s necessary for responding to email inquiries.
- Cookie Usage Notice: Informs users about the use of cookies for personalized ads (e.g., Monetag) or analytics (e.g., Google Analytics) and explains how to refuse them.
- Retention Period: States that collected information (like emails) will be destroyed immediately after its purpose is fulfilled, with an exception clause for retaining it until a legal dispute is resolved.
- Third-Party Provision and Consignment: Clarifies that data is not directly provided to third parties and lists the external services used, such as hosting (GitLab) and advertising (Monetag).
- Rights of the Data Subject: Informs users of their right to request the deletion of their information.
- Protection of Minors: States that the site does not intentionally collect information from children under the age of 16.
- Safety Assurance Measures: Describes the minimum administrative and technical measures taken to manage collected data securely (e.g., limiting access, using a trusted email service).
You can refer to the completed Privacy Policy based on these points.
Step 2: Implementing the Cookie Consent Banner – How to Obtain Consent?
Just as important as creating the policy is obtaining consent in a legally compliant way. GDPR, in particular, requires ‘Prior Consent’, which means ad or analytics scripts must not run until the user clicks the ‘Accept’ button.
To implement this flexibly and manageably on a Jekyll site, we built a system that heavily utilizes the _config.yml
file.
1. Consolidating All Settings in _config.yml
First, we’ll manage the banner text, button text, and all scripts requiring consent from within _config.yml
. This way, you won’t need to touch HTML files to modify scripts or text later.
# _config.yml
# Cookie Consent Banner Settings
cookie_consent:
enabled: true # Set this to false to disable the banner feature entirely.
banner_message: "We use cookies to ensure our site functions properly and to provide personalized ads."
accept_button_text: "Accept"
decline_button_text: "Decline"
# Consent-Based Scripts
consent_scripts:
monetag: |
(function(s,u,z,p) ... document.documentElement)
google_analytics: |
// Paste your Google Analytics script content here.
2. Modifying the Layout File (_layouts/default.html
)
Next, we create a for
loop that automatically adds the scripts defined in _config.yml
to the page. This code inserts the scripts with type="text/plain"
to keep them disabled initially.
Add the following code in an appropriate place before the closing </head>
tag.
{% for script_item in site.consent_scripts %}
{% if script_item[1] and script_item[1] != "" %}
<script type="text/plain" data-consent-script>
{{ script_item[1] }}
</script>
{% endif %}
{% endfor %}
Then, just before the closing </body>
tag, add the HTML for the banner and the link to the JavaScript file that controls it all.
{% if site.cookie_consent.enabled %}
<div id="cookie-banner">
<p>{{ site.cookie_consent.banner_message | escape }}</p>
<div>
<button id="consent-accept">{{ site.cookie_consent.accept_button_text | escape }}</button>
{% if site.cookie_consent.decline_button_text and site.cookie_consent.decline_button_text != "" %}
<button id="consent-decline">{{ site.cookie_consent.decline_button_text | escape }}</button>
{% endif %}
</div>
</div>
<script src="{{ '/assets/js/consent.js' | relative_url }}"></script>
{% endif %}
3. Writing the Core Logic (assets/js/consent.js
)
Finally, here is the JavaScript code that handles the actual consent and activates the disabled scripts. This code saves the user’s choice in localStorage
to prevent the banner from appearing repeatedly.
// assets/js/consent.js
document.addEventListener('DOMContentLoaded', function() {
const banner = document.getElementById('cookie-banner');
const acceptBtn = document.getElementById('consent-accept');
const declineBtn = document.getElementById('consent-decline');
function loadConsentScripts() {
const consentScripts = document.querySelectorAll('script[type="text/plain"][data-consent-script]');
consentScripts.forEach(script => {
const newScript = document.createElement('script');
for (let i = 0; i < script.attributes.length; i++) {
const attr = script.attributes[i];
if (attr.name.toLowerCase() !== 'type') {
newScript.setAttribute(attr.name, attr.value);
}
}
if (script.innerHTML) {
newScript.innerHTML = script.innerHTML;
}
document.body.appendChild(newScript);
});
}
acceptBtn.addEventListener('click', function() {
loadConsentScripts();
localStorage.setItem('cookie_consent', 'true');
banner.style.display = 'none';
});
if (declineBtn) {
declineBtn.addEventListener('click', function() {
localStorage.setItem('cookie_consent', 'false');
banner.style.display = 'none';
});
}
const consent = localStorage.getItem('cookie_consent');
if (consent === 'true') {
loadConsentScripts();
} else if (consent === 'false') {
banner.style.display = 'none';
} else {
if (banner) {
banner.style.display = 'block';
}
}
});
(You can add CSS code freely to match your own design.)
Conclusion: A Process of Building Trust
Now, we have a multilingual privacy policy that meets legal requirements and a flexible cookie consent system that respects user choice. This process goes beyond simply complying with the law; it’s about sending a message of trust to our users, assuring them that we value their data.
Of course, the final step is to have the policy reviewed by a legal expert. I hope this guide serves as the first step in adding transparency and trustworthiness to your project.