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:
-
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.
- The route path pattern (e.g.,
-
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.
-
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.htmlusers/102.htmlproducts/laptop.htmlproducts/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.tsdefines routes, including/users/:id.src/views/User.vueis a component that accepts auserIdprop 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.tsdefines routes, including/products/:slug.src/views/Product.vueis a component that accepts aproductSlugprop 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.htmldist/prerendered/products/keyboard.htmldist/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
paramExtractoranddataFetcherwill 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-prerenderorvue-plugin-prerenderas 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-renderercan 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 yourdataFetcherand the props of your Vue component. - The
paramExtractorshould ideally return the actual values that will be used to generate the final URL segments (e.g.,101for/users/101.html). - The
dataFetchershould return data that can be passed as props or context to your Vue component.