<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
  <title>Javier Delgado&#39;s Blog</title>
  <link>https://blog.javierdelgado.com.ve/</link>
  <description>Javier Delgado&#39;s blog, computer engineer and full-stack web developer: articles on web development, Vue, AWS, automation, and artificial intelligence.</description>
  <language>en</language>
  <atom:link href="https://blog.javierdelgado.com.ve/rss.xml" rel="self" type="application/rss+xml"/>
  <item>
    <title>How I migrated my portfolio from Vue 2 to Vue 3 with Vite (without rewriting it from scratch)</title>
    <link>https://blog.javierdelgado.com.ve/posts/migrating-my-portfolio-from-vue-2-to-vue-3-with-vite/</link>
    <guid isPermaLink="true">https://blog.javierdelgado.com.ve/posts/migrating-my-portfolio-from-vue-2-to-vue-3-with-vite/</guid>
    <pubDate>Sun, 13 Sep 2026 12:00:00 GMT</pubDate>
    <dc:creator>Javier Delgado</dc:creator>
    <category>Web Development</category>
    <description>I migrated a 2017 portfolio (Vue 2, Webpack 3, jQuery) to Vue 3 with Vite in phases, replacing every dead dependency without redoing the design. Here&#39;s the plan, the decisions, and what I learned.</description>
    <content:encoded><![CDATA[<p>My portfolio had run on the same stack for almost ten years: <strong>Vue 2.5, Webpack 3, Babel 6, jQuery 1.12</strong>, and an HTML template bought in 2016. It worked, but it no longer compiled on any modern version of Node, and every dependency had security warnings. Instead of rebuilding it with a new framework, I decided to migrate it to <strong>Vue 3 with Vite</strong> while keeping the design. This post summarizes the method I followed, which applies to any legacy Vue 2 project.</p>
<h2 id="start-with-security-not-the-framework"><a class="anchor" href="#start-with-security-not-the-framework" aria-hidden="true">#</a>Start with security, not the framework</h2>
<p>The first step wasn&#39;t touching Vue. It was reviewing what in the repository could cause harm right now:</p>
<ul>
<li>A <strong>Google Maps API key</strong> written into <code>index.html</code>, for a map that wasn&#39;t even displayed. It was removed from the code, but since it&#39;s still in the public git history, the only real fix is to rotate it and restrict it by domain.</li>
<li>A <strong>PHP contact form</strong> with no validation or protection. The Vue component already sent data to a different backend, so the PHP was dead code and a liability at the same time.</li>
<li>An integration with the <strong>Twitter API v1.1</strong>, shut down years ago.</li>
<li>The <code>dist/</code> folder checked into git, plus two <code>.zip</code> files with copies of the project.</li>
</ul>
<p>All of that can be cleaned up in an afternoon and doesn&#39;t depend on any migration. If the project stalls halfway through, at least it&#39;s left more secure than before.</p>
<h2 id="a-phased-roadmap-on-a-separate-branch"><a class="anchor" href="#a-phased-roadmap-on-a-separate-branch" aria-hidden="true">#</a>A phased roadmap on a separate branch</h2>
<p>I wrote the plan in a Markdown file inside the repository itself, with checkboxes to track progress. The phases were:</p>
<div class="table-wrap"><table><thead><tr><th>Phase</th><th>Goal</th><th>Result</th></tr></thead><tbody><tr><td>0</td><td>Immediate security</td><td>API key out of the code, PHP and Twitter removed, artifacts out of git</td></tr><tr><td>1</td><td>Environment cleanup</td><td><code>browserslist</code> updated, original HTML templates removed, unused imports gone</td></tr><tr><td>2</td><td>Vue 3 + Vite scaffolding</td><td>New Vite project, components migrated one by one</td></tr><tr><td>3</td><td>Dependency replacement</td><td>Every Vue 2 plugin replaced or removed</td></tr><tr><td>5</td><td>Styles and assets</td><td>Pending: SCSS into the Vite pipeline, icons to SVG</td></tr><tr><td>6</td><td>Deployment</td><td>GitHub Actions publishes <code>dist/</code> to GitHub Pages on every push to <code>master</code></td></tr></tbody></table></div><p>The key was working on a <code>migration/vue3</code> branch while <code>master</code> kept serving the old site. No &quot;big bang.&quot;</p>
<h2 id="vue-3-without-rewriting-components"><a class="anchor" href="#vue-3-without-rewriting-components" aria-hidden="true">#</a>Vue 3 without rewriting components</h2>
<p>Vue 3 still supports the <strong>Options API</strong>, so almost every component moved over with minimal changes: <code>new Vue()</code> became <code>createApp()</code>, filters disappeared, and the <code>eventBus</code> (an empty Vue instance in Vue 2) was replaced with <a href="https://github.com/developit/mitt" target="_blank" rel="noopener">mitt</a>, which does the same thing in 200 bytes.</p>
<p>What actually took work was everything <strong>jQuery did through <code>core.js</code></strong>, the original template&#39;s script: the page loader, the header that pins on scroll, the mobile side panel, and the project modal. Each one was rewritten as Vue behavior:</p>
<pre><code class="language-js">// App.vue — header pinned once you scroll past the top bar&#39;s height (jQuery&#39;s job in core.js before)
mounted() {
  document.body.classList.add(&#39;loaded&#39;)
  this.setHeader()
  window.addEventListener(&#39;scroll&#39;, this.setHeader, { passive: true })
},
methods: {
  setHeader() {
    const topBar = document.getElementById(&#39;top-bar&#39;)
    const limit = topBar ? topBar.offsetHeight : 0
    document.body.classList.toggle(&#39;sticky-layout&#39;, window.scrollY &gt;= limit)
  }
}
</code></pre>
<p>With that, jQuery, Bootstrap JS, Masonry, Owl Carousel, and Isotope all left the project at once.</p>
<h2 id="what-replaced-each-vue-2-dependency"><a class="anchor" href="#what-replaced-each-vue-2-dependency" aria-hidden="true">#</a>What replaced each Vue 2 dependency</h2>
<p>This was the most entertaining part. The rule: if a plugin wasn&#39;t updated for Vue 3, find the smallest possible replacement, and if what it does fits in twenty lines, write it yourself.</p>
<div class="table-wrap"><table><thead><tr><th>Vue 2</th><th>Vue 3</th><th>Note</th></tr></thead><tbody><tr><td><code>vue-multilanguage</code></td><td><code>vue-i18n</code></td><td>The <code>en</code>/<code>es</code> dictionaries moved to <code>src/i18n.js</code>; <code>v-lang.x.y</code> became <code>v-html=&quot;$t(&#39;x.y&#39;)&quot;</code></td></tr><tr><td><code>vue-carousel</code></td><td><code>@splidejs/vue-splide</code></td><td>Keeps the same carousel UX</td></tr><tr><td><code>vue-gallery</code></td><td><code>vue-easy-lightbox</code></td><td>For the certificates gallery</td></tr><tr><td><code>vue-typer</code></td><td>Custom <code>Typer.vue</code> component</td><td>Typewriter effect in 40 lines</td></tr><tr><td><code>vueisotope</code> + Masonry</td><td>CSS Grid</td><td>The projects layout no longer needs JavaScript</td></tr><tr><td><code>vue-scrollto</code></td><td>Custom <code>v-scroll-to</code> directive</td><td><code>scrollIntoView({ behavior: &#39;smooth&#39; })</code></td></tr><tr><td><code>vue-router</code>, <code>vuex</code>, <code>bootstrap-vue</code></td><td>Nothing</td><td>They were installed but never used</td></tr></tbody></table></div><p>That last row is the most important one: <strong>half the dependencies in an old project usually aren&#39;t used</strong>. Before migrating anything, check whether it&#39;s actually imported anywhere.</p>
<h2 id="vite-instead-of-webpack-3"><a class="anchor" href="#vite-instead-of-webpack-3" aria-hidden="true">#</a>Vite instead of Webpack 3</h2>
<p>The original toolchain (Webpack 3 + Babel 6) broke on Node 17 or newer because of OpenSSL changes. Upgrading Webpack from version 3 to 5 was almost as much work as switching to Vite, so I went straight to Vite:</p>
<pre><code class="language-js">// vite.config.js
import { defineConfig } from &#39;vite&#39;
import vue from &#39;@vitejs/plugin-vue&#39;
import { fileURLToPath, URL } from &#39;node:url&#39;

export default defineConfig({
  base: &#39;./&#39;, // relative paths for GitHub Pages
  plugins: [vue()],
  resolve: { alias: { &#39;@&#39;: fileURLToPath(new URL(&#39;./src&#39;, import.meta.url)) } }
})
</code></pre>
<p>The result: <code>vite build</code> finishes in a few seconds, producing 240 kB of JavaScript and 306 kB of CSS. The CSS is still large because the original template loads all of Bootstrap 3; trimming it is the next phase.</p>
<h2 id="automatic-deployment"><a class="anchor" href="#automatic-deployment" aria-hidden="true">#</a>Automatic deployment</h2>
<p>With Vite, publishing became the easy part: a GitHub Actions workflow installs dependencies, runs <code>npm run build</code>, and pushes <code>dist/</code> to GitHub Pages on every push to <code>master</code>. The <code>dist/</code> folder stopped being tracked in git.</p>
<h2 id="what-39-s-still-pending"><a class="anchor" href="#what-39-s-still-pending" aria-hidden="true">#</a>What&#39;s still pending</h2>
<ul>
<li>Move the template&#39;s SCSS into the Vite pipeline and remove unused CSS.</li>
<li>Replace icon fonts (FontAwesome, Themify) with inline SVG.</li>
<li>Run a Lighthouse audit: performance, accessibility, and SEO.</li>
</ul>
<h2 id="frequently-asked-questions"><a class="anchor" href="#frequently-asked-questions" aria-hidden="true">#</a>Frequently asked questions</h2>
<h3 id="should-i-migrate-to-vue-3-or-rewrite-with-a-different-framework"><a class="anchor" href="#should-i-migrate-to-vue-3-or-rewrite-with-a-different-framework" aria-hidden="true">#</a>Should I migrate to Vue 3 or rewrite with a different framework?</h3>
<p>If the design and components still hold up, migrating is cheaper: Vue 3 keeps the Options API, and most of the code moves over unchanged. Rewriting only makes sense if the design is changing too.</p>
<h3 id="how-long-did-the-migration-take"><a class="anchor" href="#how-long-did-the-migration-take" aria-hidden="true">#</a>How long did the migration take?</h3>
<p>Phases 0 through 3 (security, cleanup, Vue 3 + Vite, and dependency replacement) were completed over a few work sessions spread across a handful of days, because the plan was written up front and every task was small.</p>
<h3 id="what-do-i-do-about-an-api-key-that-39-s-stuck-in-git-history"><a class="anchor" href="#what-do-i-do-about-an-api-key-that-39-s-stuck-in-git-history" aria-hidden="true">#</a>What do I do about an API key that&#39;s stuck in git history?</h3>
<p>Rotate it. Deleting it from the current code doesn&#39;t help if the repository is public: anyone can read the old commits. Generate a new key and restrict it by domain (HTTP referrer) in the provider&#39;s console.</p>
<h3 id="is-it-worth-keeping-the-options-api-in-vue-3"><a class="anchor" href="#is-it-worth-keeping-the-options-api-in-vue-3" aria-hidden="true">#</a>Is it worth keeping the Options API in Vue 3?</h3>
<p>For a migrated project, yes. The Composition API is better for complex, reusable logic, but changing components that already work purely for style adds risk without immediate benefit.</p>
<h2 id="conclusion"><a class="anchor" href="#conclusion" aria-hidden="true">#</a>Conclusion</h2>
<p>Migrating a legacy project isn&#39;t one big task but many small ones: security first, then cleanup, then scaffolding, then dependencies. Writing the plan into the repository and working on a separate branch made the process predictable and kept the site running the whole time.</p>
]]></content:encoded>
  </item>
  <item>
    <title>A static blog on shared cPanel hosting: Markdown, Node, and FTP deploys</title>
    <link>https://blog.javierdelgado.com.ve/posts/static-blog-on-shared-cpanel-hosting-with-ftp/</link>
    <guid isPermaLink="true">https://blog.javierdelgado.com.ve/posts/static-blog-on-shared-cpanel-hosting-with-ftp/</guid>
    <pubDate>Thu, 10 Sep 2026 12:00:00 GMT</pubDate>
    <dc:creator>Javier Delgado</dc:creator>
    <category>Tools</category>
    <description>How this blog works, entries in Markdown, a framework-free Node generator, an .htaccess for Apache, and a script that FTPs only the files that changed. All on ordinary cPanel hosting.</description>
    <content:encoded><![CDATA[<p>This blog lives on <strong>shared cPanel hosting</strong>, the same kind of plan you get for a few dollars a month, with no Node on the server, no CI, nothing like Netlify or Vercel. Even so, publishing a static blog there is straightforward: the HTML is generated on my machine and uploaded over FTP. Here&#39;s how it&#39;s put together, in case you want to replicate it.</p>
<h2 id="why-static-and-why-cpanel"><a class="anchor" href="#why-static-and-why-cpanel" aria-hidden="true">#</a>Why static, and why cPanel</h2>
<p>A personal blog doesn&#39;t need a database or an admin panel. With HTML files:</p>
<ul>
<li>There&#39;s nothing to patch or update on the server (no more &quot;WordPress needs updating&quot; warnings).</li>
<li>The page loads in milliseconds, because Apache only has to serve files.</li>
<li>The content lives in git, as plain text, editable with any editor.</li>
</ul>
<p>And cPanel, for all its age, has exactly what&#39;s needed: an Apache server that serves folders, supports <code>.htaccess</code>, and accepts FTP uploads.</p>
<h2 id="writing-markdown-with-front-matter"><a class="anchor" href="#writing-markdown-with-front-matter" aria-hidden="true">#</a>Writing: Markdown with front matter</h2>
<p>Each post is a file under <code>posts/&lt;lang&gt;/</code> with a metadata header (front matter) and Markdown content:</p>
<pre><code class="language-markdown">---
title: Post title
description: 140-160 character summary for Google and social.
date: 2026-09-10
category: Tools
tags: [cpanel, ftp]
cover: /assets/img/my-cover.jpg
keyPoints:
  - First key idea.
  - Second key idea.
---

Content in **Markdown**…
</code></pre>
<p>The URL comes from the file name: <code>posts/en/2026-09-10-my-title.md</code> publishes at <code>/posts/my-title/</code>. If the file has <code>draft: true</code>, it isn&#39;t published. A command (<code>npm run new -- &quot;Title&quot;</code>) creates the file with the template ready to go.</p>
<h2 id="generating-a-framework-free-node-script"><a class="anchor" href="#generating-a-framework-free-node-script" aria-hidden="true">#</a>Generating: a framework-free Node script</h2>
<p>The generator is a single file, <code>build.js</code>, that does three things: reads the <code>.md</code> files, converts them to HTML with <a href="https://marked.js.org/" target="_blank" rel="noopener">marked</a>, and inserts them into HTML templates with a minimal variable engine (<code>{{ title }}</code>, <code>{{#if}}</code>, <code>{{#each}}</code>). The output goes to <code>dist/</code>:</p>
<pre><code class="language-text">dist/
├── index.html                  homepage (paginated at /page/2/, /page/3/…)
├── posts/&lt;slug&gt;/index.html     each post
├── posts/&lt;slug&gt;.md             the same post as clean Markdown
├── category/&lt;name&gt;/            category listings (categoria/&lt;nombre&gt;/ in Spanish)
├── assets/                     css, js, images
├── sitemap.xml · rss.xml · robots.txt · llms.txt · 404.html
└── .htaccess                   Apache configuration
</code></pre>
<p>No Jekyll, Hugo, or Astro. I have nothing against them, but for a blog with a custom design (the same one as the portfolio) it was faster to write 500 lines of JavaScript than to learn each tool&#39;s templating conventions. And <code>npm run dev</code> spins up a local server that mimics Apache to preview the result before uploading.</p>
<h2 id="configuring-apache-the-htaccess-file"><a class="anchor" href="#configuring-apache-the-htaccess-file" aria-hidden="true">#</a>Configuring Apache: the .htaccess file</h2>
<p>This is where it differs from a modern CDN: on cPanel, everything is configured with an <code>.htaccess</code> file that the generator writes automatically into <code>dist/</code>. The essentials:</p>
<pre><code class="language-apache">Options -Indexes
DirectoryIndex index.html
AddType text/markdown .md
ErrorDocument 404 /404.html

&lt;IfModule mod_rewrite.c&gt;
  RewriteEngine On
  RewriteCond %{HTTPS} !=on
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
&lt;/IfModule&gt;

&lt;IfModule mod_headers.c&gt;
  &lt;FilesMatch &quot;\.(css|js)$&quot;&gt;
    Header set Cache-Control &quot;public, max-age=31536000, immutable&quot;
  &lt;/FilesMatch&gt;
  &lt;FilesMatch &quot;\.(html|md|txt|xml)$&quot;&gt;
    Header set Cache-Control &quot;public, max-age=300, must-revalidate&quot;
  &lt;/FilesMatch&gt;
&lt;/IfModule&gt;

&lt;IfModule mod_deflate.c&gt;
  AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript
&lt;/IfModule&gt;
</code></pre>
<p>Clean URLs (<code>/posts/my-title/</code>) work on their own because every post is a folder with its own <code>index.html</code>. CSS and JS carry <code>?v=&lt;build-id&gt;</code> in the URL, so they can be cached for a year without risking stale versions.</p>
<h2 id="publishing-incremental-ftp-with-a-manifest"><a class="anchor" href="#publishing-incremental-ftp-with-a-manifest" aria-hidden="true">#</a>Publishing: incremental FTP with a manifest</h2>
<p>Uploading all of <code>dist/</code> on every change is slow over FTP. Instead, the deploy script stores a <code>.deploy-manifest.json</code> file on the server with the SHA-1 hash of every uploaded file. On the next deploy:</p>
<ol>
<li>It downloads the manifest and computes the hashes of <code>dist/</code>.</li>
<li>It uploads only new or modified files.</li>
<li>It deletes from the server files that were in the manifest and no longer exist (say, a deleted post). It never touches files it didn&#39;t upload itself.</li>
<li>It uploads the updated manifest.</li>
</ol>
<p>Fixing a typo in a post means uploading two files (the HTML and the <code>.md</code>) plus the feeds, not hundreds. The FTP client is the Node package <a href="https://github.com/patrickjuchli/basic-ftp" target="_blank" rel="noopener">basic-ftp</a>, and the credentials live in a <code>.env</code> file that isn&#39;t versioned:</p>
<pre><code class="language-bash">npm run deploy:dry   # connects and shows what it would upload, without touching anything
npm run deploy       # build + incremental upload
</code></pre>
<h2 id="seo-and-ai-assistants"><a class="anchor" href="#seo-and-ai-assistants" aria-hidden="true">#</a>SEO and AI assistants</h2>
<p>Since the HTML is fully generated, it&#39;s easy to include everything search engines expect: canonical tags, Open Graph, JSON-LD structured data (<code>BlogPosting</code>, <code>BreadcrumbList</code>, an automatic <code>FAQPage</code> from the FAQ section), <code>sitemap.xml</code>, and RSS.</p>
<p>And for AI assistants (ChatGPT, Claude, Perplexity), which send more and more traffic every year, the blog publishes:</p>
<ul>
<li><code>/llms.txt</code>, a site index in the <a href="https://llmstxt.org" target="_blank" rel="noopener">llmstxt.org</a> format.</li>
<li><code>/llms-full.txt</code>, with the entire content in a single Markdown file.</li>
<li>Every post at <code>/posts/&lt;slug&gt;.md</code>, linked from the HTML with <code>&lt;link rel=&quot;alternate&quot; type=&quot;text/markdown&quot;&gt;</code>.</li>
<li>An &quot;In summary&quot; block at the top of every article with the key ideas.</li>
</ul>
<h2 id="frequently-asked-questions"><a class="anchor" href="#frequently-asked-questions" aria-hidden="true">#</a>Frequently asked questions</h2>
<h3 id="does-this-work-on-a-subdomain-or-only-at-the-root"><a class="anchor" href="#does-this-work-on-a-subdomain-or-only-at-the-root" aria-hidden="true">#</a>Does this work on a subdomain, or only at the root?</h3>
<p>Both. The generator has a configurable <code>basePath</code> per language: empty if a language lives at <code>blog.mydomain.com</code>, or <code>/es</code> if it lives at <code>blog.mydomain.com/es/</code>. All internal routes, the <code>.htaccess</code>, and the sitemap adjust automatically.</p>
<h3 id="what-if-the-hosting-doesn-39-t-support-ftps"><a class="anchor" href="#what-if-the-hosting-doesn-39-t-support-ftps" aria-hidden="true">#</a>What if the hosting doesn&#39;t support FTPS?</h3>
<p>The script works with plain FTP, explicit FTPS, or implicit FTPS; it&#39;s chosen with an environment variable. If the hosting&#39;s certificate doesn&#39;t match the server name, another variable lets you accept it anyway.</p>
<h3 id="how-do-i-add-images-to-a-post"><a class="anchor" href="#how-do-i-add-images-to-a-post" aria-hidden="true">#</a>How do I add images to a post?</h3>
<p>Copy them to <code>assets/img/</code> and reference them as <code>/assets/img/file.jpg</code>. Keep covers under about 250 KB. If you replace an image, use a new file name: images are cached for a month.</p>
<h3 id="can-i-use-this-system-with-a-different-design"><a class="anchor" href="#can-i-use-this-system-with-a-different-design" aria-hidden="true">#</a>Can I use this system with a different design?</h3>
<p>Yes. The templates live in <code>layout/</code> (header, footer, post card, post page) and the styles in a single framework-free CSS file. Changing the design means editing those files.</p>
<h2 id="conclusion"><a class="anchor" href="#conclusion" aria-hidden="true">#</a>Conclusion</h2>
<p>Shared hosting isn&#39;t a limit on having a fast, well-ranked, easy-to-maintain blog. Markdown for writing, a Node script for generating, <code>.htaccess</code> for configuring Apache, and incremental FTP for publishing: four small pieces that are easy to understand in full.</p>
]]></content:encoded>
  </item>
  <item>
    <title>5 security problems I found reviewing a ten-year-old frontend</title>
    <link>https://blog.javierdelgado.com.ve/posts/5-security-problems-in-a-legacy-frontend/</link>
    <guid isPermaLink="true">https://blog.javierdelgado.com.ve/posts/5-security-problems-in-a-legacy-frontend/</guid>
    <pubDate>Sun, 06 Sep 2026 12:00:00 GMT</pubDate>
    <dc:creator>Javier Delgado</dc:creator>
    <category>Security</category>
    <description>Picking up a 2016 web project, I found exposed API keys, a vulnerable PHP form, and dead APIs. Here&#39;s the checklist to run before touching a single line of new code.</description>
    <content:encoded><![CDATA[<p>When I decided to modernize my portfolio, the first thing I did wasn&#39;t pick a framework — it was read the repository with an attacker&#39;s eyes. In a 2016 project patched on and off for years, I found five problems that are extremely common in any legacy frontend. Here they are, along with the fix I applied to each.</p>
<h2 id="1-an-api-key-in-the-html"><a class="anchor" href="#1-an-api-key-in-the-html" aria-hidden="true">#</a>1. An API key in the HTML</h2>
<p>The <code>index.html</code> had a Google Maps <code>&lt;script&gt;</code> with the API key written right into the URL. The map wasn&#39;t even shown: the <code>div</code> that held it was commented out. But the key was still there, in a public repository, available to anyone.</p>
<p><strong>What I did</strong>: delete the script. <strong>What still needs to happen</strong>: rotate the key. Removing it from the current code isn&#39;t enough, because git history keeps every previous version of the file. The new key should be restricted by domain (HTTP referrer) so it only works from the site.</p>
<blockquote>
<p>Rule of thumb: if a secret ever touched a public commit, it&#39;s compromised. Rotate it.</p>
</blockquote>
<h2 id="2-a-php-contact-form-nobody-used"><a class="anchor" href="#2-a-php-contact-form-nobody-used" aria-hidden="true">#</a>2. A PHP contact form nobody used</h2>
<p>The project included <code>contact-form.php</code>, the script that shipped with the original template to send emails. The Vue contact component already posted data to a different backend, so the PHP was dead code — and dangerous at the same time. It was still deployed and reachable by URL.</p>
<p>A script like that, with no validation or rate limiting, is a spam relay waiting to happen. <strong>What I did</strong>: delete it, and while I was at it, remove the entire contact section, which was the only reason jQuery Validate was loaded.</p>
<h2 id="3-an-integration-with-a-dead-api"><a class="anchor" href="#3-an-integration-with-a-dead-api" aria-hidden="true">#</a>3. An integration with a dead API</h2>
<p>There was an <code>api/twitter/</code> folder with code for the Twitter API v1.1, shut down years ago, complete with its own token configuration file. Dead code doesn&#39;t throw errors, so nobody reviews it — but any servable PHP file is attack surface, and any configuration file is a candidate for leaking credentials.</p>
<p><strong>What I did</strong>: delete the whole folder. If it&#39;s ever needed again, it&#39;s in the git history.</p>
<h2 id="4-build-artifacts-and-zips-inside-the-repository"><a class="anchor" href="#4-build-artifacts-and-zips-inside-the-repository" aria-hidden="true">#</a>4. Build artifacts and zips inside the repository</h2>
<p>The <code>dist/</code> folder (the compiled output) was checked into git, along with a <code>dist.zip</code> and a <code>src.zip</code>. Three copies of the project, each with its own dependency versions and potentially secrets already &quot;deleted&quot; from the source code.</p>
<p><strong>What I did</strong>: <code>git rm -r --cached dist/</code>, delete the zips, and make sure <code>.gitignore</code> excludes them. Deployment should generate the build, not read it from the repository.</p>
<h2 id="5-jquery-1-12-and-an-audit-that-surprised-me"><a class="anchor" href="#5-jquery-1-12-and-an-audit-that-surprised-me" aria-hidden="true">#</a>5. jQuery 1.12 and an audit that surprised me</h2>
<p>jQuery 1.12 has known vulnerabilities and no longer receives patches. The obvious reaction is &quot;remove it,&quot; but first you need to know who actually uses it. The audit gave a curious result: out of all the code, <strong>only the template script (<code>core.js</code>) and the form validation</strong> depended on jQuery. The Vue components didn&#39;t.</p>
<p>Once the contact section was removed (point 2), the only remaining use was in <code>core.js</code>, which got replaced by Vue behavior during the migration. The lesson: before auditing or replacing a dependency, measure its actual usage; it&#39;s often smaller than it looks.</p>
<h2 id="a-checklist-for-your-next-legacy-project"><a class="anchor" href="#a-checklist-for-your-next-legacy-project" aria-hidden="true">#</a>A checklist for your next legacy project</h2>
<p>If you&#39;re picking up an old frontend, this review fits in an afternoon:</p>
<div class="table-wrap"><table><thead><tr><th>What to look for</th><th>How</th><th>What to do</th></tr></thead><tbody><tr><td>Keys and tokens in the code</td><td><code>git log -p</code> and search for <code>key=</code>, <code>token</code>, <code>secret</code></td><td>Rotate and restrict</td></tr><tr><td>Server scripts (PHP, CGI)</td><td>List deployed executable files</td><td>Delete the ones that aren&#39;t used</td></tr><tr><td>Integrations with closed APIs</td><td>Check <code>api/</code>, <code>lib/</code>, <code>vendor/</code> folders</td><td>Delete</td></tr><tr><td>Committed artifacts</td><td><code>dist/</code>, <code>build/</code>, <code>*.zip</code> in <code>git ls-files</code></td><td>Remove from git</td></tr><tr><td>Outdated dependencies</td><td><code>npm audit</code> plus the actual <code>import</code> statements</td><td>Replace only what&#39;s really used</td></tr></tbody></table></div><h2 id="frequently-asked-questions"><a class="anchor" href="#frequently-asked-questions" aria-hidden="true">#</a>Frequently asked questions</h2>
<h3 id="do-i-really-need-to-rotate-a-key-i-already-deleted-from-the-code"><a class="anchor" href="#do-i-really-need-to-rotate-a-key-i-already-deleted-from-the-code" aria-hidden="true">#</a>Do I really need to rotate a key I already deleted from the code?</h3>
<p>Yes. In a public repository, anyone can recover the previous version of the file with <code>git log -p</code>. Even in private repositories, clones and forks retain history.</p>
<h3 id="how-do-i-know-if-an-old-php-file-is-still-reachable"><a class="anchor" href="#how-do-i-know-if-an-old-php-file-is-still-reachable" aria-hidden="true">#</a>How do I know if an old PHP file is still reachable?</h3>
<p>If it&#39;s inside the hosting&#39;s public folder (<code>public_html</code> or equivalent), it&#39;s reachable by URL even if nothing links to it. Delete it or move it outside the public folder.</p>
<h3 id="is-npm-audit-useful-for-a-2016-project"><a class="anchor" href="#is-npm-audit-useful-for-a-2016-project" aria-hidden="true">#</a>Is <code>npm audit</code> useful for a 2016 project?</h3>
<p>It&#39;s useful as an inventory, but it will produce hundreds of warnings, many for dependencies that aren&#39;t even used. Prioritize the ones actually imported in the code and the ones that run on the server.</p>
<h2 id="conclusion"><a class="anchor" href="#conclusion" aria-hidden="true">#</a>Conclusion</h2>
<p>None of these five problems required migrating the project to fix. They were addressed before touching Vue, in a separate branch, with small commits. Starting with security makes the later migration simpler, because there&#39;s less code to move, and it guarantees the effort is worthwhile even if the project stalls halfway through.</p>
]]></content:encoded>
  </item>
</channel>
</rss>
