Passing data to JavaScript within a Server-Side Rendered template

Web developers often need to add JavaScript for interactivity to their pages, and some times that interaction needs to be kicked off by some (initial) data from the backend. In this article I will show you an approach for passing data between a server-side template and JavaScript which is cleaner that what I've seen many people do in their projects.

MVC is dead, long live MVC!

In projects where the frontend is completely handled by a Single Page Application initial data may be fetched using an API call on the components "onMount" event. However, there are still a lot of (MVC) projects which just sprinkle in JavaScript in a Server Side Rendered page for extra functionality like client-side validation.

Please note by server-side rendered here I mean that the page is being rendered by say, PHP (regular PHP, Twig, Blade template etc..), Rails templates, Java with JSP, Thymeleaf, Velocity etc.. , .NET with ASP.NET pages, Razor templates, Django templates or whatever language/framework you fancy that allows you to render to HTML via some template engine.

In these kinds of projects it is not uncommon to find code that mixes in the template rendering logic with the JavaScript code on the page in a way that results in hard to read or maintain code.

NOTE: I am using PHP in these examples but I have seen this pattern in Spring Boot projects, and the solution can be applied to any framework that offers server side rendering of templates

Below is an example of code you may find in some projects when passing data from server-side template to JavaScript. (Raise your hand if you have done something like this before, I see you).

<script type="text/javascript">
    const individuals = [
        <?php foreach($people as $p): ?>
        { "firstName": <?php echo '"' . $p->firstName . '"' ?>, "lastName": <?= '"' . $p->lastName . '"' ?> },
        <?php endforeach ?>
    ];

    const selectOptions = {
        <?php echo '"' . ORGANIZER_CONSTANT . '"'?> : "Organizer",
        <?php echo '"' . SPEAKER_CONSTANT . '"'?> : "Speaker",
        <?php echo '"' . ATTENDEE_CONSTANT . '"'?> : "Attendee",
        <?php echo '"' . SPONSOR_CONSTANT . '"'?> : "Sponsor"
    }
</script>

Which would render to something like:

<script>
    const individuals = [
        { "firstName": "John", "lastName": "Banda" },
        { "firstName": "Mary", "lastName": "Banda" },
        { "firstName": "Joseph", "lastName": "Gondwe" },
        { "firstName": "Jane", "lastName": "Phiri" },
    ];

    const selectOptions = {
        "organizer": "Organizer",
        "speaker": "Speaker",
        "attendee": "Attendee",
        "sponsor": "Sponsor"
    }
</script>

This example is trivial, but I have seen situations where lots of data was serialized this way. As you can see this approach of serializing data is messy and can indeed make code hard to follow and also lead to subtle bugs:

A better way to pass data to JavaScript

We can do better by using an approach that I think is better, cleaner, separates the concerns between server-rendered template and JavaScript as well as opening up possibilities for improving testing the JavaScript code.

The approach basically works as follows:

  1. Put all the data you need on the JavaScript side into a "root" object(s) within the server-side template (note: you can use an array in PHP, a Map in Java/C# or similar languages, hashmap/dictionary in Python etc..)
  2. Encode the data as JSON using the server-side template capabilities (using json_encode in PHP, objectMapper#writeAsString or gson#toJson in a Java / Spring Boot codebase, json.dumps or to_json in Python etc..)
  3. Use JSON.parse to decode the JSON into a global object under the toplevel window, typically named the same as the root object from step 1

See the example below...

<!-- Step 1-->
<?php 
    $pageData = [
        "individuals" => $people,
        "selectOptions" => [
            ORGANIZER_CONSTANT => "Organizer",
            SPEAKER_CONSTANT => "Speaker",
            ATTENDEE_CONSTANT => "Attendee",
            SPONSOR_CONSTANT => "Sponsor"
        ],
    ];
?>

<!-- Step 2 and Step 3 -->
<script type="text/javascript" id="pageDataScript">
window.__pageData = JSON.parse(<?php echo json_encode($pageData) ?>);

const individuals = window.__pageData.individuals;
const selectOptions = window.__pageData.selectOptions;

</script>

It seems like an obvious approach in hindsight.

If you are using a template engine like Twig you can use the raw function to avoid the JSON data from being rendered using HTML escapes.

<script type="text/javascript" id="pageDataScript">
window.__pageData = JSON.parse( {{ json_encode(pageData) | raw }} );
</script>

Benefits of this approach.

  1. Cleaner - better separation of concerns as the server side code doesn't 'bleed' into the JavaScript code too much.
  2. Easier to reason about the structure of the data passed to the JavaScript handling code
  3. Makes it easy to introduce a library like VueJS or React as you could just fetch the data from the __pageData object "onMount"

Extension of the approach

An extension to the above approach enables you to render/write the data separately in a script element and then use another script element to read the data. You will note in the example below that the first <script> element has type application/json - this allows us to place data in there that the Browser safely ignores, and will not execute as JavaScript.

This may seem like overkill but could provide you with flexibility for testing if you have issues with getting the data from the backend since you can easily place random JSON that follows the proper structure in the initialPageData element.

<script type="application/json" id="initialPageData"><?php echo json_encode($pageData) </script>

<script type="text/javascript" id="pageDataScript">
window.__pageData = JSON.parse(document.getElementById("initialPageData").innerHTML);
</script>

Conclusion

In this article we have seen that with a few changes it's possible for us to make it cleaner to pass data from server-side rendered template to JavaScript to enhance the experience on pages of your web applications.

Thanks for reading. If you have questions, comments or suggestions, reach out on Twitter @zikani03.