Documentation
Feedback
Guides
Storefront Development

Storefront Development
FastStoreCustomizing pages with Dynamic Content
Step 1 - Setting up the store code for Dynamic Content
Now that you have an overview of the Dynamic Content feature, let's start the feature implementation by setting up your store code.

Instructions

1. Mapping pages and data-fetching functions

Create a file to map the home and landing Pages to their data-fetching functions. This file maps the home or the landing pages slug to a respective data fetch function, which will fetch, handle, and return the desired server-side data to display the content in a store section. For example, you can add an image from a third-party source in a Hero component.
  1. Open your FastStore project in any code editor that you prefer.
  2. In the src folder, create the dynamicContent folder.
  3. Inside the dynamicContent folder, create the index.ts file.
  4. Inside index.ts, add the following code:
    src/dynamicContent/index.ts

    _13
    const fetchDataMyLandingPage = async () => {
    _13
    const response = await fetch("https://fakestoreapi.com/products").then(
    _13
    (res) => res.json()
    _13
    );
    _13
    _13
    return { data: response };
    _13
    };
    _13
    _13
    const dynamicContent = {
    _13
    "my-landing-page": fetchDataMyLandingPage,
    _13
    };
    _13
    _13
    export default dynamicContent;

    • First, we define the fetchDataMyLandingPage, which calls for an external API (https://fakestoreapi.com/products) to fetch the product information.
    • The data object returns the information.
    • The dynamicContent relates the slug that we will define in the Headless CMS.
Let’s dive deep into dynamicContent and the pages allowed to be customized with the Dynamic Content feature: home and landing pages:
dynamicContent
The dynamicContent object stores key-value pairs:
  • The key represents the page slug (e.g., home for the Home page and my-landing-page for a specific Landing page).
  • The value is the corresponding data-fetching function responsible for fetching data for that page. In this example, fetchDataMyLandingPage.
Home
This page is a singleton page, meaning it is unique in your store. This page is mapped using the key home for the Dynamic Content feature. The value of the home key is a data-fetching function that returns the necessary data for the Home.
Landing page
Landing pages can represent different pages and URLs. To identify the landing page, use the slug from the Headless CMS SEO Path input in the Settings tab as the key to the dynamicContent object without the initial /. In the example below, the value /my-landing-page.
{"base64":"  ","img":{"width":2242,"height":1144,"type":"png","mime":"image/png","wUnits":"px","hUnits":"px","length":171788,"url":"https://vtexhelp.vtexassets.com/assets/docs/src/lpseopath___beab493e9781d1b1b8b0cdb821d9d41b.png"}}
Now that we've established the file for mapping pages to data fetching functions let's define those functions to customize your component using third-party sources.

2. Creating Dynamic Content via API fetching

After mapping pages and data-fetching functions, you need to define these functions and create the logic to fetch the data from other data sources. There are two ways for fetching data:

Using API Extensions

  1. In the store code, create the folder graphql in the' src' folder.
  2. Inside graphql, create the folder thirdParty. This folder will contain the external data that we want to obtain.
  3. Create two other folders inside the thirdParty: resolvers and typeDefs.
  4. Inside typeDefs create the extra.graphql file.
  5. Add the following code:

    _35
    type Rating {
    _35
    rate: String
    _35
    count: Int
    _35
    }
    _35
    _35
    type ProductsExtraData {
    _35
    id: Int
    _35
    title: String
    _35
    price: Float
    _35
    description: String
    _35
    category: String
    _35
    image: String
    _35
    rating: Rating
    _35
    }
    _35
    _35
    type ExtraData {
    _35
    """
    _35
    Data customizing ExtraData
    _35
    """
    _35
    data: [ProductsExtraData]
    _35
    customFieldFromRoot: String
    _35
    customField: String
    _35
    }
    _35
    _35
    type NamedExtraData {
    _35
    """
    _35
    Data customizing NamedExtraData
    _35
    """
    _35
    data: String!
    _35
    }
    _35
    _35
    type Query {
    _35
    extraData: ExtraData
    _35
    namedExtraData(name: String!): NamedExtraData
    _35
    }

    • The field ProductsExtraData represents product data, with fields for id, title, price, description, category, image, and rating.
    • The field ExtraData combines a list of ProductsExtraData and additional custom fields.
    • The field Query defines queries for fetching extraData and namedExtraData.
    Now, create the logic to fill these fields:
  6. Create a file called query.ts inside the' resolvers' folder.
  7. Add the following code to it:

    _28
    _28
    export type ProductsExtraData = {
    _28
    id: number;
    _28
    title: string;
    _28
    price: number;
    _28
    description: string;
    _28
    category: string;
    _28
    image: string;
    _28
    rating: {
    _28
    rate: number;
    _28
    count: number;
    _28
    };
    _28
    };
    _28
    _28
    async function getProductsFromAPI() {
    _28
    const response = await fetch("https://fakestoreapi.com/products");
    _28
    return response.json();
    _28
    }
    _28
    _28
    export const Query = {
    _28
    extraData: async (): Promise<{ data: ProductsExtraData[] }> => {
    _28
    const products = await getProductsFromAPI();
    _28
    return { data: products };
    _28
    },
    _28
    namedExtraData: (_: unknown, { name }: { name: string }) => ({
    _28
    data: `Named extra data: ${name}`,
    _28
    }),
    _28
    };

    • getProductsFromAPI fetches product data from an external API.
    • extraData resolves the extraData query by fetching and returning products.
    • namedExtraData resolves the namedExtraData query by returning a custom message.
  8. Create a file called extra.ts inside the' resolvers' folder.
  9. Add the following code to it:

    _13
    import { ExtraDataRoot } from "./query";
    _13
    _13
    export const ExtraData = {
    _13
    data: (root: ExtraDataRoot) => root.data,
    _13
    customFieldFromRoot: (root: ExtraDataRoot) => root?.data?.[0]?.image ?? "",
    _13
    customField: async (_: ExtraDataRoot) => {
    _13
    const res = await fetch(
    _13
    "https://fakestoreapi.com/products/category/jewelery"
    _13
    );
    _13
    const customField = await res.json();
    _13
    return (customField?.[0]?.title as string) ?? "";
    _13
    },
    _13
    };

    • The Query function returns the API data in the field extraData through a request on an external API.
  10. Inside the resolvers folder, create a file called index.ts and add the following code:

    _10
    import { ExtraData } from "./extraData";
    _10
    import { Query } from "./query";
    _10
    _10
    const resolvers = {
    _10
    ExtraData,
    _10
    Query,
    _10
    };
    _10
    _10
    export default resolvers;

  11. Combine your resolvers and queries into the store's dynamic content in the index.tsx file inside the dynamicContent folder.

    _40
    import { gql } from "@faststore/core/api";
    _40
    import { execute_unstable as execute } from "@faststore/core/experimental";
    _40
    _40
    const query = gql(`
    _40
    query ServerDynamicContent($name: String!){
    _40
    extraData {
    _40
    data {
    _40
    title
    _40
    rating {
    _40
    rate
    _40
    count
    _40
    }
    _40
    }
    _40
    customField
    _40
    customFieldFromRoot
    _40
    }
    _40
    namedExtraData(name: $name) {
    _40
    data
    _40
    }
    _40
    }
    _40
    `);
    _40
    _40
    async function fetchDataUsingApiExtension() {
    _40
    try {
    _40
    const result = await execute({
    _40
    variables: { name: "example-name" },
    _40
    operation: query,
    _40
    });
    _40
    return { data: result.data };
    _40
    } catch (error) {
    _40
    return { data: null, errors: ["Error fetching data from API Extensions"] };
    _40
    }
    _40
    }
    _40
    _40
    // Map slugs to data-fetching functions
    _40
    const dynamicContent = {
    _40
    "my-landing-page-api-extensions": fetchDataUsingApiExtension,
    _40
    };
    _40
    _40
    export default dynamicContent;

    • Imports:
      • Import gql for defining GraphQL queries.
      • Import execute_unstable for running server-side GraphQL queries.
    • query Defining the GraphQL Query:
      • Query named ServerDynamicContent accepts a name parameter.
      • Fetches fields under extraData including nested rating.
      • Fetches namedExtraData based on the name parameter.
    • fetchDataUsingApiExtension:
      • It executes to run the GraphQL query with specified variables.
      • Returns the result data or logs and returns an error if it fails.
    • dynamicContent:
      • Maps the slug my-landing-page-api-extensions to the fetchDataUsingApiExtension function.
      • This slug identifies in the code which content we want to bring to the page.
You can render this data in your store sections once you've set up your dynamic content fetching functions and mappings. See the Step 2 - Handling Dynamic Content within custom sections guide for more information on using this data in a new section.

Using Fetch API or any request library

Single request

You can use Fetch API to directly fetch data from an endpoint.

_13
_13
async function fetchDataMyLandingPage() {
_13
const response = await fetch("https://fakestoreapi.com/products");
_13
const data = await response.json();
_13
return { data };
_13
}
_13
_13
// Mapping slug to the data-fetching function
_13
const dynamicContent = {
_13
"my-landing-page": fetchDataMyLandingPage,
_13
};
_13
_13
export default dynamicContent;

Multiple Requests Example

Use Promise.all to fetch data from multiple endpoints concurrently.

_25
async function fetchDataHomepage() {
_25
try {
_25
const [apiData1, apiData2] = await Promise.all([
_25
fetch("https://fakestoreapi.com/products/1").then(res => res.json()),
_25
fetch("https://fakestoreapi.com/products/2").then(res => res.json())
_25
]);
_25
_25
return {
_25
data: {
_25
product1: apiData1,
_25
product2: apiData2,
_25
},
_25
};
_25
} catch (error) {
_25
return { data: null, error: "Error fetching data from APIs" };
_25
}
_25
}
_25
_25
// Mapping slugs to their respective data-fetching functions
_25
const dynamicContent = {
_25
home: fetchDataHomepage,
_25
"my-landing-page": fetchDataMyLandingPage,
_25
};
_25
_25
export default dynamicContent;

  • fetchDataHomepage:
    • Concurrently fetches data from two endpoints.
    • Returns the data from both endpoints in a single data object with separate keys for each product.
Once you've set up your dynamic content fetching functions and mappings, you can render this data in your store sections. For more information on using this data in a new section, see the Step 2 - Handling Dynamic Content within custom sections guide.

Returning the data Object

For both approaches, data fetching using Fetch API or Data Fetching using API extensions, the data-fetching function must return an object with data as the root key in the following format:

_10
return {
_10
data: {
_10
// data fetched
_10
},
_10
};

The data object can contain other objects inside:

_10
return {
_10
data: {
_10
key1: result1,
_10
key2: result2,
_10
},
_10
;

Contributors
1
Photo of the contributor
+ 1 contributors
Was this helpful?
Yes
No
Suggest edits (Github)
Contributors
1
Photo of the contributor
+ 1 contributors
On this page