Hone logo
Hone
Problems

Prerender Dynamic Vue.js Routes with Data Fetching

In modern single-page applications (SPAs) built with Vue.js, dynamic routes are crucial for handling variable content. However, for Search Engine Optimization (SEO) and faster initial page loads, it's beneficial to prerender these dynamic routes. This challenge focuses on creating a system to prerender dynamic routes in a Vue.js application, including fetching necessary data for each route.

Problem Description

Your task is to implement a mechanism that generates static HTML files for dynamic routes in a Vue.js application. This system should be capable of fetching specific data for each route and injecting it into the prerendered HTML. You will need to define a configuration that maps dynamic route patterns to data fetching functions and the Vue components responsible for rendering them.

Key Requirements:

  1. Dynamic Route Configuration: Define a structure (e.g., an array of objects or a map) that specifies how to handle dynamic routes. Each entry should include:

    • The route path pattern (e.g., /users/:id).
    • A method to extract the dynamic parameters from the route pattern (e.g., a function to get IDs from a data source).
    • A data fetching function that takes the extracted parameters and returns the data required for that specific route.
    • The Vue component responsible for rendering the content of that route.
  2. Data Fetching and Rendering: For each defined dynamic route, the system must:

    • Obtain the dynamic parameters (e.g., a list of user IDs).
    • For each set of parameters, call the associated data fetching function.
    • Render the corresponding Vue component with the fetched data.
    • Generate a static HTML file for each rendered route.
  3. Output Structure: The generated static HTML files should be placed in a designated output directory. The file names should correspond to the prerendered routes (e.g., users/123.html).

Expected Behavior:

Given a configuration that defines how to prerender routes like /users/:id and /products/:slug, and a data source for users and products, the system should produce static HTML files for each user and product route. For example, if there are users with IDs 101 and 102, and products with slugs "laptop" and "keyboard", the output should include:

  • users/101.html
  • users/102.html
  • products/laptop.html
  • products/keyboard.html

Edge Cases:

  • No data for a route: If a data fetching function returns no data for a specific parameter set, that route should not be generated.
  • Invalid parameter extraction: Handle cases where parameter extraction might fail.
  • Nested dynamic routes: While not the primary focus, consider how a robust solution might handle or be extended for nested dynamic segments.

Examples

Example 1: Prerendering User Profiles

Input:

  • Vue App Structure:
    • src/router/index.ts defines routes, including /users/:id.
    • src/views/User.vue is a component that accepts a userId prop and fetches user data based on it.
  • Prerender Configuration:
    const prerenderConfig = [
      {
        pathPattern: '/users/:id',
        paramExtractor: async () => ['101', '102', '103'], // Simulate fetching user IDs
        dataFetcher: async (userId: string) => {
          // Simulate fetching user data from an API
          return { id: userId, name: `User ${userId}`, email: `user${userId}@example.com` };
        },
        componentPath: './src/views/User.vue', // Path to the Vue component
      },
    ];
    
  • Component Logic (src/views/User.vue - conceptual):
    <template>
      <div>
        <h1>{{ user.name }}</h1>
        <p>Email: {{ user.email }}</p>
      </div>
    </template>
    <script setup lang="ts">
    import { defineProps, ref } from 'vue';
    const props = defineProps<{ userId: string }>();
    const user = ref<any>(null);
    
    // In a real app, this would be an async fetch based on props.userId
    async function fetchUserData() {
      // Assume this is called by the prerendering process
      // In a real setup, you'd pass the data directly or have a way to access it
      console.log("Fetching data for user:", props.userId);
      // For prerendering, the data is typically injected, not fetched again.
      // Let's assume for the purpose of this example, it's pre-fetched.
    }
    // In the context of prerendering, the data fetched by the `dataFetcher` is used.
    // We'll simulate that the `user` ref is populated by the prerendering process.
    // Example: For user '101', this component would be rendered with user data { id: '101', name: 'User 101', email: 'user101@example.com' }
    </script>
    
  • Output Directory: dist/prerendered

Output:

  • dist/prerendered/users/101.html (containing the rendered content for User 101)
  • dist/prerendered/users/102.html (containing the rendered content for User 102)
  • dist/prerendered/users/103.html (containing the rendered content for User 103)

Explanation:

The prerenderConfig defines how to handle the /users/:id route. The paramExtractor provides the IDs '101', '102', and '103'. For each ID, the dataFetcher simulates fetching user details. The prerendering process then uses these details to render the User.vue component for each user, generating separate HTML files.

Example 2: Prerendering Products with Slugs

Input:

  • Vue App Structure:
    • src/router/index.ts defines routes, including /products/:slug.
    • src/views/Product.vue is a component that accepts a productSlug prop and renders product information.
  • Prerender Configuration:
    const prerenderConfig = [
      // ... (previous user config)
      {
        pathPattern: '/products/:slug',
        paramExtractor: async () => ['laptop', 'keyboard', 'mouse'], // Simulate fetching slugs
        dataFetcher: async (slug: string) => {
          // Simulate fetching product data
          const products = {
            laptop: { name: 'Awesome Laptop', price: 1200 },
            keyboard: { name: 'Mechanical Keyboard', price: 75 },
            mouse: { name: 'Ergonomic Mouse', price: 25 },
          };
          return products[slug] ? { ...products[slug], slug } : null;
        },
        componentPath: './src/views/Product.vue',
      },
    ];
    
  • Component Logic (src/views/Product.vue - conceptual):
    <template>
      <div>
        <h1>{{ product.name }}</h1>
        <p>Price: ${{ product.price }}</p>
      </div>
    </template>
    <script setup lang="ts">
    import { defineProps, ref } from 'vue';
    const props = defineProps<{ productSlug: string }>();
    const product = ref<any>(null);
    
    // Similar to User.vue, the product data is assumed to be pre-fetched and injected.
    </script>
    
  • Output Directory: dist/prerendered

Output:

  • dist/prerendered/products/laptop.html
  • dist/prerendered/products/keyboard.html
  • dist/prerendered/products/mouse.html

Explanation:

This configuration handles the /products/:slug route. The paramExtractor yields slugs. For each slug, the dataFetcher returns product details. The Product.vue component is then rendered with this data, creating static HTML files for each product.

Constraints

  • The number of dynamic routes to prerender is not explicitly limited, but the process should be reasonably efficient for up to a few hundred dynamic routes.
  • Input for paramExtractor and dataFetcher will be primitive types (strings, numbers) or arrays of these.
  • Vue components will be standard Vue 3 components using the Composition API ( <script setup> ).
  • The system should be implemented as a Node.js script, as this is common for build processes.

Notes

  • Consider using a library like vite-plugin-prerender or vue-plugin-prerender as inspiration, but the goal is to understand and implement the core logic yourself.
  • You'll need to simulate or mock the Vue rendering process. Libraries like @vue/server-renderer can be helpful here, but you might need to set up a minimal Vue application context.
  • Think about how to map route parameters (e.g., :id) to the arguments of your dataFetcher and the props of your Vue component.
  • The paramExtractor should ideally return the actual values that will be used to generate the final URL segments (e.g., 101 for /users/101.html).
  • The dataFetcher should return data that can be passed as props or context to your Vue component.
Loading editor...
typescript