<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sayed Esmail</title>
    <description>The latest articles on DEV Community by Sayed Esmail (@sayedesmail).</description>
    <link>https://dev.to/sayedesmail</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4223278%2F9ee92b7c-4416-43f0-b97b-5c95d624997b.png</url>
      <title>DEV Community: Sayed Esmail</title>
      <link>https://dev.to/sayedesmail</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sayedesmail"/>
    <language>en</language>
    <item>
      <title>What migrating a Vuetify 2 dashboard to Vue 3 actually took</title>
      <dc:creator>Sayed Esmail</dc:creator>
      <pubDate>Tue, 03 Nov 2026 01:20:54 +0000</pubDate>
      <link>https://dev.to/sayedesmail/what-migrating-a-vuetify-2-dashboard-to-vue-3-actually-took-2nih</link>
      <guid>https://dev.to/sayedesmail/what-migrating-a-vuetify-2-dashboard-to-vue-3-actually-took-2nih</guid>
      <description>&lt;p&gt;Vue 2 reached end of life on 31 December 2023. Vuetify 2 followed on 25 January 2025. If your admin panel still runs on them, it gets no security fixes, and every SOC 2 or ISO 27001 review asks why.&lt;/p&gt;

&lt;p&gt;To show what the move involves, I migrated a well-known open-source app in public: Creative Tim's &lt;strong&gt;Vuetify Material Dashboard&lt;/strong&gt; (MIT, 1.3k GitHub stars). The full branch is here: &lt;a href="https://github.com/SayedEsmail/vuetify-material-dashboard" rel="noopener noreferrer"&gt;https://github.com/SayedEsmail/vuetify-material-dashboard&lt;/a&gt;. Every commit handles one concern and lists the breaking changes it fixed, so the commit log reads as the migration plan. I wrote the plan, did the work with an AI coding assistant, and reviewed every change myself at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vue 2.6, Vuetify 2.2, Vuex 3, Vue Router 3, Vue CLI 4 on webpack 4.&lt;/li&gt;
&lt;li&gt;33 single-file components and 5,620 lines of JavaScript and Vue.&lt;/li&gt;
&lt;li&gt;553 lines of custom SASS written against Vuetify 2's internal class names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The target was Vue 3.5, Vuetify 3, Pinia, Vue Router 4 and Vite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: delete before you migrate
&lt;/h2&gt;

&lt;p&gt;Seven of the 33 components could not be reached. Five views had no route, and two components were only used by those views. A form-validation library was registered globally and never used. The test suite still asserted the Vue CLI scaffold text, so it could never pass.&lt;/p&gt;

&lt;p&gt;Deleting that first removed a third of the code before any migration work started. It is the cheapest saving in every migration, and an audit finds it in an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Vuetify 2 projects can't migrate "gradually"
&lt;/h2&gt;

&lt;p&gt;The usual Vue 2 path is the &lt;code&gt;@vue/compat&lt;/code&gt; build: run on Vue 3 in compatibility mode, then fix warnings one by one. That path is closed when the app uses Vuetify 2, because Vuetify 2 cannot run on Vue 3 at all. The UI layer has to move in one step.&lt;/p&gt;

&lt;p&gt;So the order matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tooling first: Vue CLI to Vite, then the new dependency set.&lt;/li&gt;
&lt;li&gt;App bootstrap: &lt;code&gt;createApp&lt;/code&gt;, &lt;code&gt;createVuetify&lt;/code&gt;, the i18n adapter.&lt;/li&gt;
&lt;li&gt;State and routing: Vuex to Pinia, Router 3 to 4. These are mechanical.&lt;/li&gt;
&lt;li&gt;Vue-2-only plugins: the chart wrapper had no Vue 3 version, so it became a 56-line local component on Chartist 1.x.&lt;/li&gt;
&lt;li&gt;Vuetify, from the outside in: layout shell, then the shared component library, then the views.&lt;/li&gt;
&lt;li&gt;The custom CSS layer last.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where the real work is
&lt;/h2&gt;

&lt;p&gt;Component APIs changed a lot, but those changes are well documented: &lt;code&gt;v-list-item-content&lt;/code&gt; is gone, activator slots give &lt;code&gt;props&lt;/code&gt; instead of &lt;code&gt;on&lt;/code&gt; and &lt;code&gt;attrs&lt;/code&gt;, &lt;code&gt;v-tabs-items&lt;/code&gt; became &lt;code&gt;v-window&lt;/code&gt;. I hit 43 distinct breaking changes and logged each one. That list is now the checklist I run in every audit.&lt;/p&gt;

&lt;p&gt;The expensive surprises were elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom CSS on framework internals.&lt;/strong&gt; 470 lines targeted classes like &lt;code&gt;.theme--light&lt;/code&gt;, &lt;code&gt;.v-size--default&lt;/code&gt; and &lt;code&gt;.v-card__text&lt;/code&gt;. Not one of them exists in Vuetify 3. Nothing errors. The styles silently stop applying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behaviour that changed without an error.&lt;/strong&gt; In Vuetify 3, a navigation item linked to &lt;code&gt;/&lt;/code&gt; stays highlighted on every page unless you mark it &lt;code&gt;exact&lt;/code&gt;. Alerts now clip anything positioned outside them. The build stays green and the UI is subtly wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bugs that were already there.&lt;/strong&gt; The theme colour picker never reached dark mode because it read a property that was never defined. Turning the sidebar image back on stored the value &lt;code&gt;true&lt;/code&gt; instead of an image URL. Neither was caused by the migration, but a migration is when someone finally reads every line.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vue 3.5, Vuetify 3.13, Pinia, Vue Router 4, Vite 8, ESLint 10.&lt;/li&gt;
&lt;li&gt;26 components, 3,644 lines of JavaScript and Vue, and 350 lines of SASS in one file.&lt;/li&gt;
&lt;li&gt;The same 8 routes and the same behaviour, minus the two bugs above.&lt;/li&gt;
&lt;li&gt;A test suite that runs: every route renders in the full layout with zero Vue warnings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd tell a team planning this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run an inventory first. Dead code, undeclared dependencies and tests that can't pass are common, and they change the scope.&lt;/li&gt;
&lt;li&gt;Budget for CSS and visual QA, not only for component syntax.&lt;/li&gt;
&lt;li&gt;If you use Vuetify 2, plan the UI layer as one cut-over, behind a feature branch, with route-level smoke tests in place before you start.&lt;/li&gt;
&lt;li&gt;Decide the target versions up front. Vuetify 4 is out now. Going to Vuetify 3 first is still the shorter step from Vuetify 2, and 3 to 4 is a much smaller change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your product still runs on Vue 2, Nuxt 2 or Vuetify 2, I do a fixed-price migration readiness audit: a written report with the dead code, the breaking changes that apply to your codebase, the risks, and a fixed quote for the migration.&lt;/p&gt;

&lt;p&gt;Sayed Esmail · &lt;a href="https://github.com/SayedEsmail" rel="noopener noreferrer"&gt;https://github.com/SayedEsmail&lt;/a&gt; · &lt;a href="https://linkedin.com/in/sayed-esmail-khattab" rel="noopener noreferrer"&gt;https://linkedin.com/in/sayed-esmail-khattab&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vue</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
