This guide provides a comprehensive, step-by-step roadmap to building a passive income stream through micro-tool websites. By finding small, low-competition problems and using AI to build fast, SEO-optimized utilities, you can rank on Google and earn revenue via Google AdSense.
.com domain (~₹1,000/year).Real-World Example: The author's portfolio at mohitarora.me was built following this exact process: React + TypeScript, Cloudflare Pages, multi-theme design with CSS variables, auto-deploy from GitHub, JSON-LD structured data for SEO, custom 404/500 error pages, Sentry error monitoring, PWA with service worker caching, and a custom SPA router with scroll restoration. Every technique in this guide is battle-tested on a live production site.
The goal is to find a small problem with low competition that users are actively searching for.
Your domain name is a crucial ranking factor.
.com domain.realonlineruler.com contains online and ruler).freeruler.com is taken, try adding context like realonlineruler.com..me domains.Prepare your local machine for the AI agent to build the tool.
npm install -g @anthropic-ai/claude-code
*(Why? This command uses the Node Package Manager (npm) to install Claude Code globally (-g) on your system, allowing you to use the claude command from any terminal folder.)*
Alternatively, use free alternatives like Cursor AI or Gemini CLI / Google OpenCode Minimax.realonlineruler.com).realonlineruler.com), set it to Public, and do not initialize with a README, .gitignore, or license. 💡 Why Public? GitHub offers free private repos, but Cloudflare Pages can only connect to public repos on the free plan. Your code is already public on the internet via your website anyway.npm create astro@latest .
(Why this command? npm create fetches the latest Astro installation script. The . at the end tells it to install directly into your current empty folder rather than creating a new subfolder. Astro is chosen because it outputs static HTML, which is lightning-fast and highly favored by Google's search algorithms. 💡 What you will see: The terminal will show a progress bar downloading packages, then ask you a few questions. This is normal - it should take 20-60 seconds depending on your internet speed.)tsconfig.json file. Open it in VS Code (you will see it in the file explorer on the left). Inside the compilerOptions section, add these four settings. Your file will look like this:{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
(Why strict mode? TypeScript catches entire categories of bugs at compile time: null references, missing returns, unused variables that indicate dead code. Without strict: true, TypeScript allows many unsafe patterns that silently become runtime errors. Setting this up early means the AI generates cleaner code from the start.)git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git
(Replace YOUR_USERNAME and YOUR_REPO_NAME with your actual GitHub username and repository name. 💡 Where to find these: Your GitHub username is in the top-right corner of GitHub. Your repo name is what you named it in Step 3. The full URL is shown on your repo page in the "Quick setup" section - just copy it from there.)git push -u origin main
(What this does: git remote add connects your local folder to the GitHub repository. git push -u origin main uploads your code and sets the upstream, so future pushes can be just git push.)Before handing the project to your AI agent, set up these foundational files that every production website needs. They keep secrets safe, catch bugs, and make your CI pipeline fast.
.env file (added to .gitignore — never commit it) for API keys, analytics tokens, and other secrets. Also create .env.example as a template with placeholder values so other developers (or future you) know what variables are needed:# .env.example — copy to .env and fill in your values
VITE_SENTRY_DSN=
VITE_ANALYTICS_ID=
SENTRY_AUTH_TOKEN=
For Vite projects, all client-exposed variables must start with VITE_ — variables without this prefix are only available during build (server-side). This is a security boundary that prevents accidentally leaking server secrets to the browser.
overrides in package.json to pin specific versions and patch known CVEs:{
"overrides": {
"brace-expansion": "5.0.6"
}
}
Run npm audit regularly to check for vulnerabilities. When one is found, add it to overrides and commit the fix.
vitest.config.ts and a test setup file:npm install -D vitest happy-dom
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'happy-dom',
include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
},
})
// src/test/setup.ts — runs before every test
import '@testing-library/jest-dom'
Add test scripts to package.json:
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
wrangler.toml in your project root. This is declarative, version-controlled, and avoids manual click-through every time you set up a new project:name = "your-project-name"
pages_build_output_dir = "dist"
If you use this file, Cloudflare Pages will detect it and auto-configure the build output directory. You still need to connect the GitHub repo through the dashboard once, but all build settings come from this file.
Prompt the AI to initialize these:
Set up the project tooling: create a .env.example file with VITE_ prefixed placeholders for API keys, add npm overrides for known vulnerable packages, initialize Vitest with happy-dom for testing, and create a wrangler.toml for Cloudflare Pages build output configuration.
Give the AI the context it needs to create professional, modern designs.
Create a Design Reference File: This tells the AI what "good design" looks like. Create a new file in VS Code named @DESIGN.md in your project root folder (right-click in the VS Code file explorer on the left and select New File). Paste this into it:
Design Requirements:
- Clean, modern, professional look inspired by Vercel
- Dark theme with accent colors (blue/purple gradient)
- Mobile responsive - works on phones and tablets
- Fast loading, minimal JavaScript
- Accessible with proper contrast ratios
- Rounded corners, subtle shadows, smooth transitions
(Why? The AI needs written design guidelines because it cannot see. Without these instructions, the AI might generate ugly default styles. The @ prefix makes the file easy to reference in your prompts.)
Launch Your AI Agent: In the VS Code terminal, start your CLI:
claude
antigravity
(Alternatively, if using Cursor AI, just open the Composer/Chat panel).Install Skills / Provide Context:
claude mcp add web-design-guidelines
claude mcp add tailwind-v4-docs
(Source Links: Web Design Guidelines | Tailwind v4 Docs)@Docs feature and search for "Tailwind CSS v4" to give it context.(Purpose: AI models are trained on past data. Adding live context or MCP "skills" ensures the AI writes modern code instead of outdated versions, preventing frustrating bugs.)
Astro JS MCP Server: Use the Astro JS MCP Server so the AI knows the latest Astro syntax.
claude mcp add --transport http "Astro docs" https://mcp.docs.astro.build/mcp
{
"mcpServers": {
"Astro docs": {
"type": "http",
"url": "https://mcp.docs.astro.build/mcp"
}
}
}
Paste the following master prompt into your AI agent (adapt the bracketed text for your tool):
I have initialized a new AstroJS project. Use your available web design skills, plugins, or read modern Tailwind v4 documentation. Also use @DESIGN.md. Keep the website design like Vercel.
Name: [Your Tool Name]
Domain: [yourdomain.com]
Create a [describe your tool in detail]. My competitor is [URL] - analyze it, identify its weaknesses,
and build a better version. Use MPA (multi-page application) architecture for best SEO.
Real-World Prompt Example (from realonlineruler.com):
Create an online ruler website that will have ruler on the edges, user can select where to place the ruler. we want these 3 calibration methods Method 1: Auto-Detect Device Method 2: Screen Diagonal Method 3: Credit Card Calibration
My competitor website is https://anruler.com/ and it have some features which we need and we need to make a website better than it. Give me ideas how to make it better. go on to this website and check what exactly we need to make. Do not copy design or ui from that website.
Iterating:
npm run dev
(What this does: It starts a live preview of your website on your computer. Any changes the AI makes will automatically refresh in your browser so you can test them instantly.)http://localhost:4321 in your browser./clear
Add a Dark Mode toggle and ensure the layout is perfectly Mobile Responsive.Making your site accessible is not just about compliance; it improves SEO, broadens your audience, and is often required for AdSense approval. Ensure the AI includes these fundamentals:
h1 → h2 → h3), <nav> for navigation, <main> for primary content, and <footer> for footer content. Screen readers rely on this structure.aria-label to icon-only buttons, aria-expanded to toggle/menu buttons, and role attributes where semantic HTML is insufficient.tabindex where needed) and visible focus indicators are present (:focus-visible styles).<img> needs descriptive alt text. Inform the AI: "Add descriptive alt text to all images based on their content."rel="noopener noreferrer" for security and aria-label indicating they open externally.Prompt the AI:
Make the website fully accessible: add proper ARIA labels, semantic HTML structure, keyboard navigation support, alt text on images, and rel="noopener noreferrer" on all external links.
These patterns prevent common CSS bugs that plague AI-generated sites and ensure your styles hold up across browsers and devices:
body { overflow-x: hidden } — it breaks position: sticky by clipping the sticky element's container. If you need horizontal overflow hidden, set it on <html> instead:/* ✅ Correct */
html { overflow-x: hidden; }
/* ❌ Wrong — breaks sticky positioning */
body { overflow-x: hidden; }
prefers-reduced-motion media query to disable them when the user requests it:@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
@media print {
nav, button, .no-print, input[type="checkbox"] { display: none !important; }
body { background: #fff !important; color: #000 !important; }
a { color: #000 !important; }
pre { background: #f5f5f5 !important; border: 1px solid #ddd; }
.collapsed-section { max-height: none !important; opacity: 1 !important; }
}
.card {
background: #0a1122; /* fallback for old browsers */
background: var(--bg-secondary); /* modern override */
color: #94a3b8; /* fallback */
color: var(--text-secondary); /* modern override */
}
auto-fill / auto-fit with minmax() to create responsive layouts that automatically adjust to container width:.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
Prompt the AI to apply these CSS improvements:
Apply these CSS fixes: move overflow-x: hidden from body to html, add a prefers-reduced-motion media query that disables all animations, add @media print styles that hide interactive elements, add CSS variable fallbacks for older browsers, and use auto-fill/minmax for responsive grids.
These battle-tested component patterns solve common UX problems that AI code often gets wrong:
Instead of React's built-in lazy() (which loads when the component mounts, not when it's visible), use IntersectionObserver to trigger imports only when the user scrolls near the section. The rootMargin starts loading before the user reaches it, eliminating perceived delay:
const LazyOnViewport = ({ importFn, fallback }) => {
const [Component, setComponent] = useState(null);
const [error, setError] = useState(null);
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
observer.disconnect();
importFn()
.then(mod => setComponent(() => mod.default))
.catch(err => setError(err));
}
},
{ rootMargin: '300px 0px', threshold: 0 }
);
observer.observe(el);
return () => observer.disconnect();
}, [importFn]);
if (error) return <div>Failed to load. <button onClick={() => setError(null)}>Retry</button></div>;
return <div ref={ref}>{Component ? <Component /> : fallback}</div>;
};
Pair this with shimmer skeletons — animated gradient placeholders that match the section's dimensions — instead of a plain loading spinner:
.shimmer {
background: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.04) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 12px;
}
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
A proper FAQ accordion avoids display: none (which breaks height transition). Instead, use a max-height transition for smooth animation, close the current item when opening another, and add a hover timer for a premium feel:
const FaqAccordion = ({ items }) => {
const [openIndex, setOpenIndex] = useState(null);
const hoverTimer = useRef(null);
return items.map((item, i) => (
<div
key={i}
className="faq-item"
onMouseEnter={() => {
hoverTimer.current = setTimeout(() => setOpenIndex(i), 1000);
}}
onMouseLeave={() => clearTimeout(hoverTimer.current)}
>
<button
className="faq-question"
onClick={() => setOpenIndex(openIndex === i ? null : i)}
aria-expanded={openIndex === i}
>
{item.question}
</button>
<div
className="faq-answer"
style={{
maxHeight: openIndex === i ? '500px' : '0',
overflow: 'hidden',
transition: 'max-height 0.35s ease',
}}
>
<p>{item.answer}</p>
</div>
</div>
));
};
Every code block and contact detail should have a copy button. Use navigator.clipboard.writeText() with a brief "Copied!" feedback state:
const CopyButton = ({ text }) => {
const [copied, setCopied] = useState(false);
const copy = async () => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Copy failed:', err);
}
};
return (
<button className="copy-btn" onClick={copy}>
{copied ? 'Copied!' : 'Copy'}
</button>
);
};
Prompt the AI to implement these patterns:
Implement these component patterns: lazy-load sections with IntersectionObserver using shimmer skeleton placeholders and a 300px rootMargin, build a FAQ accordion with smooth max-height transitions and hover-to-auto-open, and add copy-to-clipboard buttons on all code blocks and contact information.
🟡 OPTIONAL - SKIP THIS for now. Your site works fine with a simple dark/light mode. This advanced multi-theme system (dark, light, matrix, cyberpunk) is something you can add after your site is live. Do not get stuck here.
Beyond basic dark/light mode, you can build a full multi-theme system that lets users choose their preferred look (dark, light, matrix, cyberpunk, etc.). This sets your micro-tool apart from competitors and creates a memorable user experience.
:root, then override them per theme::root {
--bg-color: #040914;
--text-primary: #f8fafc;
--accent-color: #00f0ff;
--accent-rgb: 0, 240, 255;
}
[data-theme="matrix"] {
--bg-color: #000000;
--text-primary: #00ff41;
--accent-color: #00ff41;
--accent-rgb: 0, 255, 65;
}
[data-theme="light"] {
--bg-color: #ffffff;
--text-primary: #0f172a;
--accent-color: #0284c7;
--accent-rgb: 2, 132, 199;
}
rgba() (like rgba(var(--accent-rgb), 0.4)) must be redefined in every theme block. A missing variable silently falls back to the default theme, which looks broken. Always verify all theme blocks have the complete set of variables.<head> to prevent it:<meta name="darkreader-lock" />
<script>
(function() {
var t = localStorage.getItem('site_theme');
if (t) document.documentElement.setAttribute('data-theme', t);
})();
</script>
<meta name="color-scheme" content="light dark" /> to your <head>. This tells the browser your site natively supports both color schemes. Without it, Chrome may forcibly override your custom themes with its own dark mode algorithm, breaking your carefully designed colors.<head> to opt out:<script>document.documentElement.style.setProperty('forced-color-adjust', 'none');</script>
<script>
(function() {
var meta = document.createElement('meta');
meta.name = 'theme-color';
// Match your default theme's background color
meta.content = '#040914';
document.head.appendChild(meta);
})();
</script>
On theme change, update meta.content to the new theme's --bg-color.requestAnimationFrame (RAF) state machine for paint-guaranteed crossfades:// Returns an inline style object that blocks rendering until the transition starts
function getThemeTransitionStyle(isTransitioning) {
if (!isTransitioning) return {};
return {
animation: 'themeFade 0.45s cubic-bezier(0.25, 0.8, 0.25, 1)',
// The keyframes simply crossfade opacity
};
}
// RAF double-tick guarantees the browser has painted the new theme colors
// before the reveal animation starts
⚠️ For React, Vue, or Svelte SPAs only. Astro generates static HTML pages by default - there is hardly any JavaScript to split. If you are using Astro (as this guide recommends), you can skip this entire section. This is only relevant if you chose a JavaScript framework like React.
For sites built with frameworks (React, Vue, Svelte), loading all JavaScript upfront slows down the initial page load. Split your code so only essential code loads first:
React.lazy() or dynamic import() for sections below the fold (e.g., testimonials, blog, FAQ). This shrinks the initial bundle by 30-50%.// Priority order: lower number = loads sooner
const priorityMap = {
projects: 1,
education: 2,
recommendations: 3,
resume: 4,
};
// vite.config.ts
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('react-dom')) return 'vendor-react';
if (id.includes('framer-motion')) return 'vendor-motion';
if (id.includes('react-icons')) return 'vendor-icons';
}
}
}
Prompt the AI:
Split the website into lazy-loaded sections. The hero, about, and experience sections should load immediately. Projects, education, recommendations, and resume should use dynamic imports.
Beyond code splitting, these techniques improve how quickly the browser renders the initial viewport — directly impacting Core Web Vitals (LCP, FCP, TBT) and Google rankings:
<head> with fetchpriority="high" so the browser discovers it immediately instead of waiting for the CSS to load:<link rel="preload" as="image" href="/hero.webp" type="image/webp" fetchpriority="high" />
<link rel="preconnect"> for every third-party origin your page fetches from (Google Fonts, analytics, CDNs). This lets the browser start the DNS + TCP + TLS handshake earlier:<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
dns-prefetch — lighter than preconnect but still resolves DNS early:<link rel="dns-prefetch" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://cdn.thirdparty.com" />
<script>history.scrollRestoration='manual';window.scrollTo(0,0);</script>
<noscript>
<div style="padding:2rem;background:#040914;color:#f8fafc;font-family:system-ui;text-align:center;">
<h1>Your Site Name</h1>
<p>Email: <a href="mailto:you@example.com" style="color:#00f0ff;">you@example.com</a></p>
<p>GitHub: <a href="https://github.com/yourhandle" style="color:#00f0ff;">github.com/yourhandle</a></p>
</div>
</noscript>
esbuild over lightningcss as the CSS minifier. lightningcss strips -webkit- prefixed properties like -webkit-backdrop-filter, breaking glassmorphism effects on Safari:// vite.config.ts
build: {
cssMinify: 'esbuild', // preserves -webkit-backdrop-filter
}
Prompt the AI to apply these for your specific project:
Add preconnect links for Google Fonts, dns-prefetch for third-party APIs, preload the main hero image with fetchpriority high, add a branded noscript fallback, and disable automatic scroll restoration. Also set the CSS minifier to esbuild in vite.config.ts.
Make the site look legitimate and trackable.
/public folder.favicon.svg file.I have added new favicon files to the public folder. Please add the HTML favicon link tags to the global site header.
Add this Google Analytics tracking code to the global site header.
git add . && git commit -m "Add Google Analytics" && git push so the tracking goes live.(⚠️ Important: Cloudflare Pages automatically injects basic edge analytics. If you add Google Analytics too, you might see different numbers and could double-count visits. See Common Pitfall #5 below for details. For now, adding Google Analytics is fine -- just do not add two GA snippets.)
Google needs text to understand what your tool does. Pure tool pages don't rank.
/clear first to save tokens):Do the on-page SEO of this website for:
Main Keyword: [your keyword]
Supporting Keywords: [comma-separated list of keywords you found in Ahrefs]
Add these SEO essentials to the global site header:
1. A `` tag to prevent duplicate content penalties.
2. Full Open Graph meta tags including `og:image:width` (1200), `og:image:height` (630), `og:image:type` (image/jpeg), and `og:image:alt` for social share previews.
3. `twitter:creator` with your @handle and a proper `twitter:card` tag.
4. ``.
Write 600 words of highly optimized, user-friendly text about the tool on the home page for SEO purposes. Make sure the page URL structure is clean.
This captures long-tail search traffic and triggers Google Rich Snippets.
Prompt the AI:
Add an SEO-friendly FAQ section to the homepage using JSON-LD structured data. Use these exact questions:
[Paste the list of questions you copied from Ahrefs (https://ahrefs.com/keyword-generator) and Google's (https://google.com) "People Also Ask"]
Example JSON-LD Format the AI should generate:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Can I use my phone as a ruler?",
"acceptedAnswer": {
"@type": "Answer",
"text": "<p>Yes, by using the screen diagonal or credit card calibration methods, you can accurately use your phone screen as a ruler.</p>"
}
}
]
}
</script>
(Why JSON-LD Structured Data? JSON-LD is a machine-readable code format that Google understands perfectly. Instead of just displaying text on a screen, JSON-LD explicitly tells Google "this page has an FAQ section" using schema.org vocabulary. This often results in Google showing your questions directly in search results as rich snippets, expanding them inline so users see answers without clicking. Beyond FAQs, you can also use JSON-LD for breadcrumbs, reviews, how-to guides, and articles to boost your site's search visibility.)
Beyond FAQPage, the single most impactful structured data type for a personal site or portfolio is the Person schema. It tells Google who you are, what you do, where you work, and links to all your profiles — directly influencing Knowledge Panel results and E-E-A-T signals.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Your Full Name",
"jobTitle": "Senior Software Engineer",
"url": "https://yourdomain.com/",
"sameAs": [
"https://www.linkedin.com/in/yourprofile/",
"https://github.com/yourhandle",
"https://twitter.com/yourhandle"
],
"email": "mailto:you@example.com",
"knowsAbout": ["Java", "Spring Boot", "React", "TypeScript"],
"worksFor": [
{ "@type": "Organization", "name": "Current Company" }
]
}
</script>
Prompt the AI to add this to your site's global header:
Add JSON-LD Person structured data to the site header with my name, job title, LinkedIn, GitHub, Twitter profiles, email, skills, and current employer.
For a business or micro-tool site, replace Person with Organization or WebSite schema to describe the business behind the tool. If you have both a personal brand and a business, add both — they can coexist in separate <script> blocks.
Google AdSense will reject your site without standard legal pages. Beyond AdSense, these pages serve critical purposes for every website. Below is what each file does, why it matters, and how to create them.
| Page | What It Is | Why You Need It |
|---|---|---|
| Privacy Policy | A legal document explaining what personal data your site collects (cookies, analytics, contact forms) and how you use it. | Required by law (GDPR in EU, CCPA in California). Google AdSense mandates it. Failing to have one can result in fines. |
| Terms & Conditions | Rules users agree to by using your site: copyright terms, liability disclaimers, acceptable use policy. | Protects you legally. Required by AdSense. Without it, users could misuse your tool or claim damages. |
| About Us | A page describing who built the site and why. | Builds user trust. Google uses E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) as a ranking signal: an About page proves these. |
| Contact Us | A way for users and legal authorities to reach you (email, form, or address). | Legitimacy signal. Required by GDPR (must have a contact point). Users need to report issues or bugs. |
| File | What It Is | Why You Need It |
|---|---|---|
| sitemap.xml | An XML file listing every page on your site with metadata (last updated, priority, change frequency). | Google''s primary way of discovering your pages. Without it, some pages may never get indexed, especially on new sites. |
| robots.txt | A text file that tells search engine crawlers which URLs they can and cannot access. | Prevents crawlers from wasting quota on admin pages or duplicate content. Must reference your sitemap URL. |
| 404 Error Page | The page shown when a user visits a URL that doesn''t exist. | Retains users who hit broken links; without it they see a browser error page and leave immediately. A good 404 page guides users back to useful content. Make it engaging — add ambient particle animations, quick-link buttons to main sections, and a prominent "Back to Home" button. A boring 404 page still loses users. |
| 500 Error Page | The page shown when the server encounters an internal error. | Professional polish. When things break (and they will), a branded 500 page is far better than a raw error message. |
Your robots.txt file lives at the root of your website's source folder (/public for Astro/static sites, /public for any framework). It should explicitly invite all crawlers to index every page. Your native file should look exactly like this:
User-agent: *
Allow: /
Sitemap: https://your-domain.com/sitemap.xml
User-agent: * — applies the rules to every known crawler.Allow: / — wide-open, permits indexing of the entire site. Do not use Disallow unless you have admin pages you want to hide.Sitemap: — tells crawlers where to find your sitemap. Google, Bing, and others will discover all your pages from this file.Once you have this file in your project's /public folder and deployed the site to Cloudflare Pages, you must disable Cloudflare's own robots.txt generation so they don't override your native file. See Step 12: Cloudflare Visibility & AI Crawler Configuration for details.
Beyond the pages above, these files are critical for every production website:
| File | What It Is | Why You Need It |
|---|---|---|
| Favicon | A small icon that appears in browser tabs, bookmarks, and search results. | Brand recognition. A missing favicon looks unprofessional. Google also displays it next to your site in search results. |
| OG Meta Tags (Open Graph) | HTML meta tags that control how your page appears when shared on social media (Twitter, WhatsApp, LinkedIn). | Without OG tags, social shares show a generic title and no image, killing click-through rates. Essential for any site that gets shared. |
| ads.txt | A text file that declares which ad networks are authorized to sell ads on your site. | Required by Google AdSense. Prevents unauthorized ad fraud. Without it, AdSense earnings are blocked. |
| _redirects (Cloudflare Pages) or .htaccess (Apache) | A configuration file that defines URL redirect rules and SPA fallback behavior. | Essential for single-page apps (React, Vue, Svelte) to prevent 404 errors on page refresh. Also used for permanent redirects without code changes. |
| _headers (Cloudflare Pages) | A configuration file that sets HTTP security and indexing headers on specific paths. | Used to block the free .pages.dev subdomain from being indexed (prevents duplicate content SEO penalty), and to set security headers like X-Content-Type-Options. |
Create these pages as separate MPA routes for best SEO:
- Privacy Policy (with GDPR/CCPA compliance language)
- Terms & Conditions
- About Us
- Contact Us (with a working contact form or email link)
Make these pages clearly visible and linked in the home page footer and header.
Also, generate a custom 404 error page, a 500 error page, a sitemap.xml, and a robots.txt file that links to the sitemap.
Add proper OG meta tags to the global site header and generate a favicon package in the /public folder.
/public and /src/pages folders:
Only if you used React, Vue, or Svelte (not Astro):
(Why? Astro generates static HTML files, so there is no SPA refresh issue. You still need _headers for security, but it is covered in Step 11 below.)
Cloudflare Pages provides free, lightning-fast hosting for static sites. The best approach is to connect your GitHub repository so every push deploys automatically.
⚠️ CRITICAL: Select "Pages" not "Worker"
When you click Create application, Cloudflare's default tab is Worker (not Pages). You must manually switch to the Pages tab before proceeding. Creating a Worker instead of Pages will not work for a static website - this is an extremely common mistake that can waste a lot of your time.💡 Workers vs. Pages - Which one for your site?
Cloudflare Pages is for hosting complete websites - HTML, CSS, JS, and static assets. It connects directly to your GitHub repo and auto-deploys. This is what you want for your Astro micro-tool site.
Workers is for writing small serverless functions (APIs, redirects, backend logic) that run on Cloudflare's edge. It is not for hosting a full website.
Rule of thumb: If you have a Git repo with HTML files → use Pages. If you are writing a single JavaScript function to handle API requests → use Worker.
realonlineruler.com) and click Begin setup.main.npm run build.dist.npm run build, and deploy the output to a free *.pages.dev subdomain. This takes about 1–2 minutes.git add . && git commit -m "your update message" && git push
Cloudflare automatically detects the push, rebuilds your site, and deploys the update - no manual deploy commands needed.
✅ Astro MPA users: SKIP THIS. Astro generates real HTML files for every page (/privacy/index.html, /about/index.html, etc.). There is no 404-on-refresh issue. You do NOT need _redirects.
If you built a single-page application (SPA) instead of a multi-page static site, navigating directly to a URL like yoursite.com/about or refreshing the page will return a 404, because the server has no real about.html file. The SPA handles routing in the browser, not the server.
The fix: Create a file called _redirects in your /public folder with this exact content:
/* /index.html 200
What this does: It tells Cloudflare Pages to serve index.html for every unknown URL path. Your JavaScript app reads the URL and displays the correct page. The 200 status tells search engines it is a successful response (not a redirect), which is critical for SEO.
404 safety net: Even with SPA fallback, always create a public/404.html file. It only shows in rare edge cases (e.g., a bot hitting a truly invalid path), but it is far better than a raw browser error.
Beyond redirects, every production site should set proper security and caching headers. Create a file called _headers in your /public folder:
# Long-lived asset cache (JS/CSS/fonts with content hash in filename)
/assets/*
Cache-Control: public, max-age=31536000, immutable
# Static media: cache with stale-while-revalidate so stale copies serve instantly
# while the browser fetches a fresh version in the background
/avatars/*
Cache-Control: public, max-age=604800, stale-while-revalidate=86400
/favicon.*
Cache-Control: public, max-age=604800, stale-while-revalidate=86400
/og-banner.* /og-image.*
Cache-Control: public, max-age=604800, stale-while-revalidate=86400
# Service worker: NEVER cache — must always get the latest version
# Service-Worker-Allowed grants it control over the entire site scope
/sw.js
Cache-Control: no-store, no-cache, must-revalidate
Service-Worker-Allowed: /
# Web app manifest: short TTL so icon/name changes propagate quickly
/manifest.webmanifest /manifest.json
Cache-Control: public, max-age=3600, must-revalidate
# Everything else: no cache, security headers applied globally
/*
Cache-Control: public, max-age=0, must-revalidate
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
# Block the free .pages.dev subdomain from search engines
https://*.pages.dev/*
X-Robots-Tag: noindex, nofollow
What these do: Built assets (JS/CSS) get long-term immutable caching for instant repeat visits. Static media (icons, OG images) use stale-while-revalidate — immediately serve the stale copy while refreshing in the background. The service worker (sw.js) is explicitly set to no-store so updates roll out instantly, with Service-Worker-Allowed: / giving it control over the entire site scope. The web app manifest gets a short TTL so icon/name changes propagate quickly without requiring a full SW update. Security headers prevent clickjacking (X-Frame-Options), enforce HTTPS (Strict-Transport-Security), and restrict browser features (Permissions-Policy). The X-Robots-Tag rule blocks the free .pages.dev subdomain from being indexed, preventing duplicate content SEO penalties.
🟡 OPTIONAL - SKIP FOR NOW. PWA lets users install your site on their phone like an app. It is not required for Google ranking or AdSense. Come back to this after your site is earning money.
Make your site installable on users' home screens and work offline. This gives your micro-tool a native app experience without the App Store.
npm install vite-plugin-pwa. For Next.js, use next-pwa.public/manifest.webmanifest with your app metadata. SVG icons are preferred over PNG (smaller, resolution-independent, no pixelation on retina displays):{
"name": "Your Site Name - Tagline",
"short_name": "Short Name",
"description": "SEO-friendly description of your tool",
"start_url": "/",
"display": "standalone",
"background_color": "#040914",
"theme_color": "#040914",
"lang": "en",
"scope": "/",
"orientation": "portrait-primary",
"icons": [
{ "src": "/icons/icon-192.svg", "sizes": "192x192", "type": "image/svg+xml", "purpose": "any" },
{ "src": "/icons/icon-512.svg", "sizes": "512x512", "type": "image/svg+xml", "purpose": "any" },
{ "src": "/icons/icon-maskable.svg", "sizes": "512x512", "type": "image/svg+xml", "purpose": "maskable" }
]
}
Include a maskable icon (with padding/safe-zone) for Android adaptive icons — they get cropped into circles/squirccles by the OS. The purpose: "maskable" field tells the browser to apply the safe-zone cropping.
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Short Name" />
<link rel="apple-touch-icon" href="/icons/icon-192.svg" />
<link rel="manifest" href="/manifest.webmanifest" />
Without these, iOS users get no splash screen, a generic icon, and the browser chrome (URL bar) stays visible — defeating the app-like experience.
runtimeCaching: [
{
// NetworkFirst: Try network first, fall back to cache.
// Best for API calls where freshness matters.
urlPattern: /^https:\/\/api\.example\.com\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 50, maxAgeSeconds: 3600 },
networkTimeoutSeconds: 5,
},
},
{
// StaleWhileRevalidate: Serve cached instantly, refresh in background.
// Best for avatars, CDN assets, media files.
urlPattern: /^https:\/\/cdn\.example\.com\/.*/i,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'cdn-cache',
expiration: { maxEntries: 100, maxAgeSeconds: 604800 },
},
},
]
The Service Worker (SW) is auto-generated by the plugin. To ensure the SW always updates instantly when you deploy new code, add these configuration flags:
// vite.config.js / next.config.js
VitePWA({
registerType: 'autoUpdate', // SW updates silently in background
workbox: {
skipWaiting: true, // Activate new SW immediately
clientsClaim: true, // Take over all open tabs
cleanupOutdatedCaches: true, // Remove old cache versions
},
})
yourdomain.com/manifest.webmanifest and shows the install prompt (a + icon in the address bar).Why PWA matters: Google rewards fast, installable experiences. A PWA-enabled micro-tool loads instantly for returning visitors. Users can add it to their home screen like a native app, increasing engagement and repeat usage, both positive signals for SEO.
Prompt the AI to set up PWA for your project:
Set up PWA for this project: create a manifest.webmanifest file in /public with SVG icons (192, 512, maskable), add Apple-specific meta tags for iOS, configure runtime caching with NetworkFirst for API calls and StaleWhileRevalidate for CDN assets, and enable auto-update service worker with skipWaiting and clientsClaim.
🟡 SKIP THIS for your first site. You only need the main branch. Cloudflare Pages will auto-deploy everything you push to main. Branch previews are useful for teams, not for a single-person micro-tool project. Come back here later.
Cloudflare Pages can deploy different branches to different environments. This lets you test changes before they go live:
| Branch | Deploys To | When to Use |
|---|---|---|
main | Your production domain (yourdomain.com) | Only fully tested, ready-to-ship code |
dev | A preview URL (project-name.pages.dev) | Daily work-in-progress; test before merging to main |
| Feature branches | No deployment (skip preview) | Experimental or single-feature work |
How to set up: In Cloudflare Dashboard → your project → Settings → Builds & Deployments → Branch Controls. Remove the wildcard * branch rule, then explicitly add only dev and main. This prevents every random branch from triggering a preview, saving build minutes.
⏭️ SKIP THIS for your first website. Cloudflare Pages already auto-deploys every time you push to GitHub (you set this up in Step 11). You do NOT need a separate CI/CD pipeline. Come back here once your site is live and you want extra pre-deploy checks.
Cloudflare Pages auto-deploys on every push, but what if your code has TypeScript errors or failing tests? Set up a GitHub Actions pipeline to catch problems before deployment:
.github/workflows/ci.yml with this config:name: CI
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
build-and-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint code
run: npm run lint
- name: Run tests
run: npm test
- name: Build project
run: npm run buildnpx husky-init && npm install to set up Git hooks that run lint and tests before every commit, catching issues even earlier.[skip ci] to commit messages for documentation-only changes (this is already covered in Common Pitfalls #8).cache: 'npm' line caches node_modules between CI runs, cutting install time from ~60s to ~10s on repeat runs. Without it, every CI run downloads all dependencies from scratch.(Why CI/CD matters: Cloudflare Pages only shows you build failures after a deploy attempt. GitHub Actions catches failures in the PR stage, before anything goes live. This is especially important when AI generates code that might have subtle TypeScript errors or broken imports.)
Now that the site is fully built and deployed, buy your .com domain. Spaceship is the best overall choice - it's a modern platform (sister company of Namecheap) offering low prices on both registration and renewals, a clean dashboard without upsells, and free WHOIS privacy. Alternatives include Namecheap (reliable but high renewal costs), GoDaddy, BigRock, or Hostinger.
💡 Domain Purchasing Tips:
- Double-Check Spelling: Verify the spelling multiple times. Once paid, you cannot change or refund a domain name!
- 1-Year Plan: Registrars often default to a 3-year plan in your cart. Manually change this to 1-year to save upfront costs.
- Auto-Renewal: Turn OFF auto-renewal in your registrar account settings. You should only renew next year if the site is generating income.
.com domain and select the Free plan.A@8.8.8.8.com domain and activate it (add the www version too).After updating nameservers at your registrar, DNS changes can take anywhere from 10 minutes to 48 hours to propagate worldwide. Verify before proceeding:
nslookup -type=ns yourdomain.com (or Resolve-DnsName -Name yourdomain.com -Type NS in PowerShell) for a quick local check.Now that your site is deployed to Cloudflare Pages and DNS is resolving, optimize it for maximum discoverability by configuring Cloudflare's visibility settings. Perform the following steps in order.
Before touching Cloudflare, ensure your project's /public directory contains a robots.txt that explicitly invites all crawlers. If you followed the Technical SEO Files section above (Step 10), yours should already have this content:
User-agent: *
Allow: /
Sitemap: https://your-domain.com/sitemap.xml
This file is now in your project source. The next step is to tell Cloudflare to stop overriding it.
Why: Cloudflare's default mode intercepts your native Allow: / and may accidentally set Disallow rules. By disabling their configuration, you ensure your absolute Allow remains and no search engines are accidentally blocked.
Why: Choosing "Block on all pages" stops AI assistants (Claude, ChatGPT, Gemini) at the network firewall. By selecting "allow", recruiters and others who use AI search tools or live-summarizing extensions can perfectly view and index your work.
This configuration acts as an open invitation. While a massive corporation would block these bots to save bandwidth, your priority is to be found by the right person using the right tool.
| Setting | Your Selection | The Engineering Impact |
|---|---|---|
| Robots.txt Management | Disable Cloudflare Override | Keeps your native Allow: / active. |
| AI Training Bots | Do Not Block | Ensures AI tools can parse your tech and site description for talent search. |
| Edge Firewall Status | Completely Open | Bypasses Cloudflare's strict default changes for crawlers. |
Your site is now accessible at both yourdomain.com and yoursite.pages.dev. Google penalizes this duplicate content. (Note: If you decided to use Cloudflare Workers instead of Pages, you must explicitly disable the .workers.dev domain routing in your Cloudflare dashboard after connecting your .com domain).
_headers inside your project's /public folder..pages.dev URL):https://yoursite.pages.dev/*
X-Robots-Tag: noindex
.com domain:git add . && git commit -m "Add _headers to block pages.dev indexing" && git push
.pages.dev URL in Google Chrome.X-Robots-Tag: noindex (and often other security headers like x-content-type-options: nosniff). This confirms that the duplicate content issue is successfully fixed and Google will ignore this subdomain!Force search engines to crawl your new site.
realonlineruler.com - enter it exactly like that, without https:// or www).TXT, type @ in the Name field, and paste the copied TXT record into the Content field. Save it.https://realonlineruler.com/robots.txt).https://realonlineruler.com/sitemap-index.xml).https://, e.g., https://realonlineruler.com), and press Enter.https://realonlineruler.com) and click submit. (Why? Bing powers DuckDuckGo, Yahoo, and many AI search engines-it's highly worth the 5 minutes).Congratulations! Your website is now fully live and indexed.
⏳ Timeline Expectations:
- Performance Data: It will take about 2 days before Google Search Console starts showing any traffic data for your page.
- Site Icon: It will take roughly 1 week for your website's favicon to appear next to your site in Google search results.
Ensure your website is optimized for speed, which is a major Google ranking factor (Core Web Vitals).
https://realonlineruler.com) and click Analyze.https://pagespeed.web.dev/analysis/...).Analyze the Lighthouse report for my live site from this link: [Paste PageSpeed URL]. Fix all highlighted issues to improve my mobile performance score. Focus on minimizing JavaScript bundle size, reducing main-thread blocking time, and optimizing critical rendering paths.
Back in Step 7, you added the Google Analytics tracking code to your website. Now that your site is fully live on the internet, you must verify the installation to track your users.
🟡 OPTIONAL - SKIP FOR NOW. For a simple micro-tool, you probably do not need Sentry error monitoring. Your users will email you if something breaks. Come back and set this up after your site is getting traffic.
Your website will have bugs, especially if AI generated most of the code. Catch them before users report them. Error monitoring tools capture JavaScript exceptions, network failures, and unexpected state so you can fix issues proactively.
npm install @sentry/react
import * as Sentry from '@sentry/react'
Sentry.init({
dsn: 'YOUR_DSN_HERE',
tracesSampleRate: 1.0,
})
// Optional: mask potentially sensitive URLs from breadcrumbs
Sentry.addGlobalEventProcessor((event) => {
if (event.request?.url) {
event.request.url = event.request.url.replace(/token=.*/, 'token=REDACTED');
}
return event;
});
import { ErrorBoundary } from '@sentry/react'
<ErrorBoundary fallback={<div>This section failed to load</div>}>
<Projects />
</ErrorBoundary>
window.addEventListener('unhandledrejection', (event) => {
Sentry.captureException(event.reason, {
tags: { type: 'unhandledrejection' },
});
});
<script> tags or early module imports) will be lost. Buffer them and replay after init:// Buffer to hold errors during SDK init
const _errorBuffer = [];
const _captureException = (err, ctx) => {
_errorBuffer.push({ err, ctx });
};
// After Sentry.init, replay the buffer
_errorBuffer.forEach(({ err, ctx }) => {
Sentry.captureException(err, { tags: { fromBuffer: 'true', ...ctx } });
});
_errorBuffer.length = 0; // clear
// Expose globally so any component can report errors
window.captureException = (err, ctx) => _captureException(err, ctx);
This also lets you expose window.captureException as a global helper, so any component can report errors without importing Sentry directly.
import() for lazy-loaded sections, the import might fail (network glitch, deploy mismatch). Wrap each dynamic import with an error state and a Retry button:const [error, setError] = useState(null);
const [Component, setComponent] = useState(null);
useEffect(() => {
import('./SomeSection')
.then(mod => setComponent(() => mod.default))
.catch(err => { setError(err); window.captureException?.(err); });
}, []);
if (error) {
return (
<div>
<p>Failed to load this section.</p>
<button onClick={() => setError(null)}>Retry</button>
</div>
);
}
Sentry.captureException(new Error('Test error - remove before deploy'));
Check your Sentry dashboard within 30 seconds. If the error appears, monitoring is working. Remove the test line after verification.
Pro tip: Set up email alerts in Sentry so you know the moment a user hits a bug. This turns a potential bad review into a fix deployed within minutes.
Do not apply for AdSense on day one. You need to wait at least 1 month before applying so your site has time to age and build trust. You MUST also ensure your Google Analytics shows you are getting a minimum of 10+ consistent organic users per day (check this in Analytics under Real-time → Users). Applying too early with zero traffic will result in an instant rejection.
https:// or www, e.g., realonlineruler.com).Add this AdSense verification script to the global site header: [paste snippet]
git add . && git commit -m "Add AdSense script" && git push. Wait 10 seconds.ads.txt is Not Found.ads.txt option and copy the publisher ID code it gives you.Create an ads.txt file in the public directory with this content: [paste your AdSense publisher ID text]
git add . && git commit -m "Add ads.txt" && git push.yourdomain.com/ads.txt in your browser).git add . && git commit -m "Fix AdSense issues" && git push, and reapply. You have to be persistent).*If you need to change your website's primary domain, you must transfer search engine authority (link juice) and redirect users seamlessly without hurting your SEO.
🛠️ Interactive Migration Helper: Enter your domains and project target to dynamically customize the code snippets and terminal commands in this step:
⚠️ CRITICAL RULE: Never simply delete your old domain or remove the redirect after a few months. Keep the old domain active and redirecting for at least 12 months (ideally forever). If you let it expire, automated bots will buy it and host spam or malicious pages, ruining your old portfolio links on PDFs, resumes, and forum posts.
newdomain.me and select the Free plan.💡 Trouble Tip: Deleting the default records will show a warning popup: "Without DNS records, Cloudflare is unable to activate your site..." This is completely normal. Click Confirm because Cloudflare Pages will handle mapping your records later.
newdomain.me, choose Custom Nameservers, delete the default ones, and paste the two Cloudflare nameservers.nslookup -type=ns newdomain.me in your terminal. Once active, the domain status in Cloudflare will turn green.newdomain.me, and click Continue. Repeat the process for the www.newdomain.me subdomain.💡 Trouble Tip: If Cloudflare shows a "Verifying/Complete DNS setup" page and asks you to add records manually: Go to your new domain's DNS > Records tab in Cloudflare, click Add record, and add:
• Type: CNAME | Name: @ | Target: yourproject.pages.dev | Proxy status: Proxied (Orange Cloud ON)
• Type: CNAME | Name: www | Target: yourproject.pages.dev | Proxy status: Proxied (Orange Cloud ON)
Then return to the Pages configuration tab and click Check DNS records.
olddomain.me and click the three dots next to it.www.olddomain.me version as well).⚠️ Why this is critical: Cloudflare Pages hosting always takes priority over Redirect Rules. If your old domain olddomain.me is still mapped directly to Pages, Cloudflare will serve the website directly and bypass any redirect rules you create in the next step.
olddomain.me) site dashboard in Cloudflare.Permanent Domain Migration Redirectconcat("https://newdomain.me", http.request.uri.path)301 (Moved Permanently)curl -I http://olddomain.me (it should return 301 with the location header pointing to the new domain).<link rel="canonical" href="https://newdomain.me/" />)og:url, og:image, etc. pointing to newdomain.me)newdomain.me)olddomain.me into the sameAs array as a historical alias).💡 Trouble Tip (Analytics Cleanup): If you use Cloudflare's Automatic setup for Web Analytics, Cloudflare automatically injects the tracking script. Go to your Pages dashboard settings, delete the environment variable VITE_CLOUDFLARE_ANALYTICS_TOKEN, and redeploy. This prevents double-tracking script conflicts on your new domain.
newdomain.me to Google Search Console as a Domain property (do not use URL prefix).
💡 Trouble Tip: Since your domain is hosted on Cloudflare, Search Console will ask you to verify via Cloudflare. Simply authorize the popup; the verification completes automatically without needing manual copy-pasting of TXT strings.
💡 Trouble Tip: For Domain properties, Search Console requires you to type the complete URL (e.g. https://newdomain.me/sitemap.xml) to avoid "Invalid sitemap address" errors.
olddomain.me) in Search Console, go to Settings → Change of address.newdomain.me, click Validate and Update, and ignore any warnings about sample redirects not being active yet. Click Confirm Move.
newdomain.me immediately.Based on real-world experience building and deploying websites, here are the most common mistakes and how to avoid them:
The Mistake: Purchasing a domain before you have a working prototype, then discovering your idea requires paid APIs, complex logic, or does not work as expected.
The Fix: Build the full tool with AI first, test it locally at localhost:4321, and only then buy the domain. Domains cost real money; do not waste it on an idea you have not validated.
The Mistake: Clicking "Create application" in Cloudflare and unknowingly creating a Worker instead of a Pages project. Workers cannot serve static websites; they are for serverless functions.
The Fix: When clicking "Create application", immediately switch from the default Worker tab to the Pages tab. Pages is for full websites; Workers is for API endpoints. Always double-check which tab you are on.
The Mistake: Defining CSS custom properties like --accent-rgb: 59, 130, 246 only in :root (the default theme) but forgetting to redefine them in alternate theme blocks like [data-theme="dark"]. This causes colors to silently break when users switch themes because the RGB fallback uses the wrong values.
The Fix: When implementing dark mode or multiple themes, every CSS variable (including the RGB variants used with rgba()) must be explicitly set in each theme''s CSS block. A missing variable silently falls back to the default, which looks broken.
The Mistake: Building a React, Vue, or Svelte site and deploying it without a _redirects file. Refreshing on yoursite.com/about makes the server look for a real /about file, which does not exist, so it returns 404.
The Fix: Add a _redirects file with /* /index.html 200 to your /public folder. This tells the server to serve the SPA entry point for all unknown paths, letting JavaScript handle routing.
Note for Astro users: This pitfall does NOT apply to you. Astro generates real HTML files for every page, so there is no 404-on-refresh issue. You can skip this.
The Mistake: Adding a Google Analytics or Cloudflare Web Analytics JS snippet to your HTML while Cloudflare Pages is already injecting its own edge analytics. This causes every page visit to be counted twice.
The Fix: Cloudflare Pages automatically injects basic edge analytics. Your manual JS snippet can coexist if you need more detailed tracking, but be aware the numbers will differ. Never add two JS snippets from the same analytics provider.
The Mistake: Continuing a long AI conversation for hours without clearing context. The AI''s context window fills up, it starts making mistakes, forgets earlier instructions, and each prompt becomes slower and more expensive.
The Fix: Run /clear before asking for each major new feature. Commit your working code to Git first so you can always revert if the AI breaks something in the new session.
The Mistake: Letting the AI modify your code for an hour without committing to Git. If the AI makes a catastrophic change, you cannot easily revert.
The Fix: Run git add . && git commit -m "checkpoint before [feature]" before every major AI prompt. Git is your undo button; use it generously.
The Mistake: Pushing documentation changes, README updates, or comment-only commits and triggering a full Cloudflare Pages build + deploy, wasting time and build minutes.
The Fix: Include [skip ci] in your commit message, e.g. git commit -m "Update README [skip ci]". This tells Cloudflare Pages to skip the build for changes that do not affect the actual website code.
The Mistake: Samsung Internet has its own built-in dark mode that forcefully overrides your site's carefully designed themes. It inverts colors, breaks contrast ratios, and makes your site look terrible, and users on Samsung devices (a huge Android market share) will see a broken version of your site.
The Fix: Add <meta name="darkreader-lock" /> to your <head> to prevent Samsung Internet's dark mode from applying. Also add <meta name="color-scheme" content="light dark" /> so the browser knows your site supports both. For stubborn cases, users may need to open internet://flags in Samsung Internet and disable "Force dark mode".
.com domain and map in Cloudflare._headers to block .pages.dev indexing._redirects for SPA routing (if using React/Vue/Svelte).Credits:
- Original Concept: Extracted from Compile Future: How to Earn Using AI
- Enhanced & Expanded By: Mohit Arora - Added GitHub to Cloudflare Pages CI/CD automation, Spaceship domain recommendations with promo codes, Workers vs Pages architecture guide, Cloudflare +Add button navigation, interactive domain input widget, checklist persistence with localStorage, copy-to-clipboard code blocks, dark theme glassmorphism UI, SPA security headers, PWA service worker setup, multi-theme CSS architecture, error monitoring with Sentry, code-splitting performance patterns, GitHub Actions CI/CD pipeline, TypeScript strict mode setup, accessibility basics guide, Samsung Internet dark mode fix, and comprehensive step-by-step refinements throughout.
- Portfolio: mohitarora.me - Live production site built using this guide
- GitHub: github.com/Arora-Sir