Vue.js Development Best Practices and Common Solutions

Router Parameter Passing Between List and Detail Pages

When navigating from a product list page to a detail page, passing a product ID is essential:

<router-link :to="{path: 'detail', query: {productId: 1}}">View Details</router-link>

The resulting URL becomes http://localhost:8080/#/detail?productId=1, with the parameter persisting even after page refresh. Access the parameter using this.$route.query.productId.

Vue offers two parameter passing methods: query parameters and params with dynamic routes.

Key Differences:

  1. Route Navigasion: Query uses path, params uses name
<!-- Query with path -->
<router-link :to="{path: 'Detail', query: { id: 1 }}">Detail</router-link>
<!-- Params with name -->
<router-link :to="{name: 'Detail', params: { id: 1 }}">Detail</router-link>
  1. Parameter Reception: Query via this.$route.query, params via this.$route.params
// Query parameters
created() {
    const productId = this.$route.query.id;
}
// Params parameters
created() {
    const itemId = this.$route.params.id;
}
  1. URL Format: Query appears as /detail?productId=1&user=123, while params with dynamic routes shows as /detail/123

  2. Route Definition Requirement: Params with dynamic routes must be defined in the route configuration:

{
    path: '/detail/:id',
    name: 'Detail',
    component: Detail
}

Undefined params parameters will be lost upon page refresh, making them unreliable for operations requiring persistent parameters.

Cross-Origin API Requests in Development

Browser security policies often block cross-origin requests during development. Configure webpack dev server proxy to resolve this:

// webpack.config.js or vue.config.js
module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: 'http://jsonplaceholder.typicode.com',
                changeOrigin: true,
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    }
}

Restart the development server after configuration. Set axios base URL to simplify API calls:

axios.defaults.baseURL = '/api';

UI Library On-Demand Loading

Reduce bundle size by importing only necessary components:

npm install vant -S
npm install babel-plugin-import -D

Configure Babel (.babelrc):

{
    "plugins": [
        ["import", {
            "libraryName": "vant",
            "libraryDirectory": "es",
            "style": true
        }]
    ]
}

Component usage:

import { Button, List } from 'vant';
Vue.use(Button).use(List);

Scoped Style Overrides for UI Components

Use deep selectors to modify third-party component styles within scoped CSS:

/* For LESS/SASS */
.van-tabs /deep/ .van-ellipsis {
    color: blue;
}

/* For plain CSS */
.van-tabs >>> .van-ellipsis {
    color: blue;
}

Timer Management

Prevent memory leaks by properly cleaning up timers:

export default {
    data() {
        return {
            intervalId: null
        }
    },
    mounted() {
        this.intervalId = setInterval(() => {
            console.log('Timer running');
        }, 1000);
        
        // Auto-cleanup on component destruction
        this.$once('hook:beforeDestroy', () => {
            clearInterval(this.intervalId);
        });
    },
    beforeDestroy() {
        clearInterval(this.intervalId);
    }
}

Mobile Responsive Adaptation

Create rem.js for responsive scaling:

(function(win, doc) {
    const docEl = doc.documentElement;
    const resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';
    const recalc = function() {
        const clientWidth = docEl.clientWidth;
        docEl.style.fontSize = (clientWidth >= 750) ? '100px' : 100 * (clientWidth / 750) + 'px';
    };
    recalc();
    win.addEventListener(resizeEvt, recalc, false);
})(window, document);

Import in main.js: import './config/rem'

Advanced Carousel with Vue-Awesome-Swiper

For complex carousel requirements beyond basic UI libraries:

npm install vue-awesome-swiper --save

Component implementation:

<template>
    <swiper :options="carouselOptions" ref="myCarousel">
        <swiper-slide><div class="slide-item">Slide 1</div></swiper-slide>
        <swiper-slide><div class="slide-item">Slide 2</div></swiper-slide>
        <div class="swiper-pagination" slot="pagination"></div>
    </swiper>
</template>

<script>
import 'swiper/dist/css/swiper.css';
import { swiper, swiperSlide } from 'vue-awesome-swiper';

export default {
    components: { swiper, swiperSlide },
    data() {
        return {
            carouselOptions: {
                pagination: { el: '.swiper-pagination' },
                navigation: {
                    nextEl: '.swiper-button-next',
                    prevEl: '.swiper-button-prev'
                }
            }
        }
    }
}
</script>

Production Build Optimization

Disable source maps in production:

// config/index.js or vue.config.js
module.exports = {
    productionSourceMap: false
}

Remove 300ms touch delay on mobile:

npm install fastclick -S
import FastClick from 'fastclick';
FastClick.attach(document.body);

Component Option Organization

Maintain consistent option ordering for better readability:

  1. Side Effects: el
  2. Global Awareness: name, parent
  3. Component Type: functional
  4. Template Modifiers: delimiters, comments
  5. Template Dependencies: components, directives, filters
  6. Composition: extends, mixins
  7. Interface: inheritAttrs, model, props/propsData
  8. Local State: data, computed
  9. Events: watch, lifecycle hooks
  10. Non-reactive Properties: methods
  11. Rendering: template/render, renderError

Bundle Analysis

Visualize bundle composition:

npm run build --report

Route Lazy Loading

Improve initial load performance:

const ProductDetail = () => import('@/views/ProductDetail.vue');

export default new Router({
    routes: [{
        path: '/product/:id',
        component: ProductDetail
    }]
});

Gzip Compression

Enable production gzip compression:

npm install compression-webpack-plugin@1.1.11 -D
// config/index.js
build: {
    productionGzip: true
}

Scoped CSS and Deep Selectors

Scoped CSS adds data attributes to selectors. Use deep selectors for child component styling:

.parent-component >>> .child-element {
    margin: 10px;
}

/* For preprocessors */
.parent-component /deep/ .child-element {
    margin: 10px;
}

Performance Monitoring with Hiper

Install global performance tool:

npm install hiper -g

Usage examples:

hiper example.com
hiper -n 100 "example.com?param=value"
hiper -n 50 "example.com" --no-cache

Data Fetching Strategies

Post-Navigation Fetching (with loading states)

<template>
    <div class="page-container">
        <loading-spinner v-if="isLoading" />
        <error-display v-else-if="hasError" />
        <page-content v-else-if="dataLoaded" :content="pageData" />
        <empty-state v-else />
    </div>
</template>

<script>
export default {
    data() {
        return {
            isLoading: true,
            hasError: false,
            dataLoaded: false,
            pageData: null
        }
    },
    async created() {
        try {
            this.pageData = await this.fetchData();
            this.dataLoaded = true;
        } catch (error) {
            this.hasError = true;
        } finally {
            this.isLoading = false;
        }
    }
}
</script>

Pre-Navigation Fetching

export default {
    beforeRouteEnter(to, from, next) {
        api.fetchProductDetail(to.params.id).then(response => {
            next(vm => {
                vm.productInfo = response.data;
                vm.loadComplete = true;
            });
        });
    }
};

Custom Component Two-Way Binding

Implement v-model for custom components:

<!-- Parent component -->
<custom-modal v-model="modalVisible"></custom-modal>

<!-- Child component -->
<template>
    <div class="modal" v-show="internalValue">
        <button @click="closeModal">Close</button>
    </div>
</template>

<script>
export default {
    model: {
        prop: 'visible',
        event: 'visibility-change'
    },
    props: {
        visible: {
            type: Boolean,
            default: false
        }
    },
    computed: {
        internalValue: {
            get() { return this.visible; },
            set(value) { this.$emit('visibility-change', value); }
        }
    },
    methods: {
        closeModal() {
            this.internalValue = false;
        }
    }
};
</script>

Route Module Organization

Split routes by feature modules:

// router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import productRoutes from './modules/products';
import userRoutes from './modules/users';

Vue.use(Router);

export default new Router({
    routes: [
        ...productRoutes,
        ...userRoutes
    ]
});

// router/modules/products.js
export default [
    {
        path: '/products',
        component: () => import('@/views/Products.vue'),
        meta: { title: 'Products' }
    }
];

Shared Utility Mixins

Create reusable functionality with mixins:

// mixins/formatting.js
import { formatCurrency, formatDate } from '@/utils/helpers';

export default {
    filters: {
        mixinCurrency(value) {
            return formatCurrency(value);
        },
        mixinShortDate(value) {
            return formatDate(value, 'short');
        }
    }
};

// Component usage
import formattingMixin from '@/mixins/formatting';
export default {
    mixins: [formattingMixin]
};

Asset Path Configuration

Configure build paths for proper asset resolution:

// config/index.js
module.exports = {
    build: {
        assetsPublicPath: './'
    }
};

// Build utility for CSS background paths
// build/utils.js
cssLoader: {
    loader: 'css-loader',
    options: {
        sourceMap: options.sourceMap
    }
}

Tags: vuejs javascript frontend web-development Router

Posted on Tue, 03 Nov 2026 16:23:18 +0000 by sajidfiaz