Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Svelte and kentico kontent.ai
Domenik Reitzner
Domenik Reitzner

Posted on • Edited on

     

Svelte and kentico kontent.ai

This blog post is about adding preview functionality to server-side-rendered CMS content from kentico kontent.ai (in my case we used Salesforce commerce cloud for the rendering). If you use already client rendering for your CMS content, than you don't need this, just add a preview config to your project.

Index

  1. Get your main site ready
  2. Proxy server with polka
  3. Sveltify your site
  4. Make the preview content togglable
  5. Add more CMS items

Get your main site ready

One prerequisite for this whole shenanigan to actually work, is that you have your live site up and running.
Another important step is that you have a way of referencing your ssr content to an kontent.ai id. The way I did it was by usingdata-system-id in the ssr site.

Proxy server with polka

The node server (I used polka, but express or any similar should work as well) is a very simple one.
I check if I get a call with a?previewId={id}, which will have the kentico id.

constdir=join(__dirname,'../public');//dir for publicconstserve=serveStatic(dir);polka().use('/preview',serve).get('*',async(req,res)=>{leturl=req.originalUrl;constisMainRequest=url.match(/(\?|&)previewId=/)!==null;// some magic 🦄}).listen(PORT,(err)=>{if(err)throwerr;console.log(`> Running on localhost:${PORT}`);});
Enter fullscreen modeExit fullscreen mode

All requests, that are not our main request we will just proxy.

if(!isMainRequest){returnrequest.get(url).auth(usr,pwd,false)// if server needs basic auth.pipe(res);}
Enter fullscreen modeExit fullscreen mode

For our main request it is important, that we remove our custom Url parameter

consttoRemove=url.match(/[\?|&](previewId=.*?$|&)/)[1];url=url.replace(toRemove,'').replace(/\/\?$/,'');
Enter fullscreen modeExit fullscreen mode

After that we can handle our main request and inject our js/css bundles at the end of our html

// get requested site from live serverconstresp=awaitfetch(url,{headers});lettext=awaitresp.text();// add script tag before </body>if(text.includes('<html')){constbundles=`        <script src="/preview/bundle.js" async></script>        <link rel="stylesheet" href="/preview/bundle.css">    `;if(text.includes('</body>')){text=text.replace('</body>',`${bundles}</body>`)}else{// cloudflare eg. minifies html// by truncating last closing tagstext+=bundles;}}// return responsereturnres.end(text);
Enter fullscreen modeExit fullscreen mode

Sveltify your site

The best choice for the frontend in my opinion (especially for such small an powerful tool) is svelte.

I leaves a small footprint comes with huge capabilities and is ideal if you want to run a tool on top of another site.

The basic svelte setup (with ts) looks something like this:

<!-- App.svelte --><scriptlang="ts">import{onMount}from'svelte';// INIT VARSletpreview=true;letaddMode=false;lettoggleFuncs=newMap();letarrayOfCmsNodes=[];letoverlays=[];onMount(()=>{// some init stuff});</script><main></main>
Enter fullscreen modeExit fullscreen mode

CSS can be totally custom. In my project I put the tools in the bottom right corner, but this is just my preference, so I'll leave them out.

In the onMount function I initialize the app by getting the previewId and setting up all available dom nodes that have cms capability. (in my case I excluded child cms components)

// App.svelteonMount(()=>{// get param from urlconsturl=newURL(document.URL);constid=url.searchParams.get('previewId');loadPreview(id);consttempArr=[];document.querySelectorAll('[data-system-id]').forEach((node:HTMLElement)=>{if(node.dataset.systemId===id)return;// for nested this needs to exclude children data-system-idif((node.parentNodeasHTMLElement).closest('[data-system-id]')!==null)return;tempArr.push(node);});arrayOfCmsNodes=tempArr;});
Enter fullscreen modeExit fullscreen mode

As you can see, the next step was to callloadPreview(id). This will get the preview data from Kontent.ai

// App.svelteimport{getPreviewContent}from'./service/kontent';import{getToggle}from'./service/toggleFunctionGenerator';constloadPreview=async(id:string)=>{if(!id)return;constcontent=awaitgetPreviewContent(id);if(!content?.items?.length)return;consttoggle=getToggle(id,content);if(!toggle)return;toggleFuncs.set(id,toggle);if(preview)toggle();}
Enter fullscreen modeExit fullscreen mode

To get the content you just need to fetch the content by id fromhttps://preview-deliver.kontent.ai/${projectId}/items?system.id=${key} by setting an authorization header with your preview key.

constheaders={'authorization':`Bearer${previewKey}`};
Enter fullscreen modeExit fullscreen mode

Make the preview content togglable

As we want the content to not be only replaced, but toggle between live and preview version, we need to generate a toggle function.

For switching between those states I created a simple toggle switch and function.

<!-- App.svelte --><scriptlang="ts">importTogglefrom'./components/Toggle.svelte';consttogglePreviews=()=>{preview=!previewtoggleFuncs.forEach(func=>func());}</script><main><Toggle{preview}{togglePreviews}/></main>
Enter fullscreen modeExit fullscreen mode

Setting up the toggle function was a little bit more complex, but in the end it is really easy to add more entries.

// .service/toggleFunctionGenerator.tsimport{replaceText,}from'./replaceContent';import{getToogleDataByType,}from'./toggleConfig';constgetNodeBySystemId=(id:string)=>document.querySelector(`[data-system-id='${id}']`);consthandleType=(type:string,id:string,elements:IKElements,modularContent:IKModularContent):{():void}=>{constnode=getNodeBySystemId(id);if(!node)returnnull;const{textReplace,}=getToogleDataByType(type,elements);constchildren=Object.keys(modularContent).length?Object.entries(modularContent).map(([key,value])=>handleType(value.system.type,value.system.id,value.elements,{})).filter((child)=>!!child):[];consttoggleFunc=()=>{if(textReplace)replaceText(node,textReplace);};returntoggleFunc;};exportconstgetToggle=(id:string,content:IKContent)=>{constitem=content.items[0];returnhandleType(item.system.type,id,item.elements,content.modular_content)};
Enter fullscreen modeExit fullscreen mode

By wrapping everything into a toggle function, we keep the state available inside of it. As kontent.ai will return a lot of data that will not be used, I decided to explicitly save the data that I need. I this inside ofgetToogleDataByType.

// .service/toggleConfig.ts// in my project I have 6 different data generators, so they ended up in a new fileconstgetGenericElements=(elements:IKElements,keyMapper:IKeyValue):IReplacer[]=>{consttempArr:IReplacer[]=[];Object.entries(keyMapper).forEach(([key,querySelector])=>{constdata=elements[key]if(!data)return;tempArr.push({querySelector,value:data.value,});});returntempArr;};// Toggle Data ConfigconstmyType=(elements:IKElements):IToggleData=>{consttextKeyMapper:IKeyValue={my_title:'.js-my_title',};return{textReplace:getGenericElements(elements,textKeyMapper),}};exportconstgetToogleDataByType=(type:string,elements:IKElements):IToggleData=>{constcallRegistry={myType:myType,}constcaller=callRegistry[type];returncaller?Object.assign({},caller(elements)):{};}
Enter fullscreen modeExit fullscreen mode

Each replacer will give us an array with objects that will match the preview value with the dom selector (or whatever other stuff you can think of).

So how does the data generation actually translate to updating the dom when the toggle function is called?
It is basically just getting and saving the old value and setting the new one.

// .service/replaceContent.tsconstgetElementByQuerySelector=(node:Element,querySelector:string):any=>querySelector===null?node:node.querySelector(querySelector);exportconstreplaceText=(node:Element,textElements:IReplacer[])=>{textElements.forEach(({querySelector,value},i)=>{constelement=getElementByQuerySelector(node,querySelector);if(!element)return;constold=element.textContent;element.textContent=value;textElements[i].value=old;});};
Enter fullscreen modeExit fullscreen mode

So we've got the basics up and running. But to only have one id previewed is a little boring.

Add more CMS items

As we alread have an array of cms nodes, setting this up should be fairly easy. ☺
We just need an overlay and handle the add click with the already existing setup.

<!-- App.svelte --><scriptlang="ts">importAddButtonfrom'./components/AddButton.svelte';importAddBoxfrom'./components/AddBox.svelte';consthandleAddClick=(idToAdd:string)=>{handleAddMode();loadPreview(idToAdd);arrayOfCmsNodes=arrayOfCmsNodes.filter((node:HTMLElement)=>node.dataset.systemId!==idToAdd);}consthandleAddMode=()=>{addMode=!addMode;if(addMode){arrayOfCmsNodes.forEach((node:HTMLElement)=>{const{top,height,left,width}=node.getBoundingClientRect();overlays.push({id:node.dataset.systemId,top:top+window.scrollY,height:height,left:left,width:width,});})overlays=overlays;}else{overlays=[];}}</script><main>    {#if arrayOfCmsNodes.length}<AddButton{addMode}{handleAddMode}/>    {/if}</main>{#each overlays as {id, top, height, left, width}}<AddBox{id}{top}{height}{left}{width}{handleAddClick}/>{/each}
Enter fullscreen modeExit fullscreen mode

I know this part was by far the easiest one, but is adds a lot of value to the functionality, so I wanted to include it here.

Thank you for reading and I hope you can take away something or are inspired for your own project.

Credits

cover image:https://unsplash.com/@marvelous

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Christian, husband, father of four, web-enthusiast, Sveltian, musician and blogger.
  • Location
    Vienna
  • Education
    School for mechatronics with emphasis on robotics
  • Work
    Senior Frontend Developer @ woom
  • Joined

More fromDomenik Reitzner

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp