 So yeah, as said, my name is Christian, most of you probably know me for the non-fins that basically means a cat, just some context, but not here to talk about myself, but instead the thing I worked the last year on. So what I did was revamp the web service with a single page application. And yes, I'm in a bit of a hurry here, so I have a lot of slides. So excuse me if I tangle in my words. So this is the finished product. So yeah, I have the screen here. So basically it's a finished technology magazine, and my mic is a bit better. So yeah, it's a tech magazine, it has a lot of technology articles, it has a weekly digital magazine, this is a new thing. Before this new site it was only published twice a month, but when we did this, we opted to once a week. But enough of this, you can check it out if you want to. I don't know if anyone heard that, but you might ask why would you build this one if you've seen the previous one. But yeah, so first of all, this started as an MVP to see if headless WordPress would suit a client's purposes. And yeah, the new digital magazine was one thing, the old site is so legacy that it was hard to implement such a thing in it anymore. And it also serves as a building foundation for the whole media group. The media group has like 20 different magazines similar to this, so we can use this as a general building block. And the client wanted to adapt modern software development practices. And last but not least, the old site was pretty awful. Just one thing to note about it's subscriber page, payloads, page loads took sometimes over 30 seconds, and yeah, if you have a user page that takes 30 seconds to load, that's not good. But how would you build this? The most obvious ones, add filter, add action, should be the self-explanatory. So every time we wanted to change something, we did this. And we hooked these, a few thousand lines of code with them. So to talk about a few thousand lines of code, I basically built a neural resolver. Those who saw me in Uvascular might have remembered that. And we did some custom end points as well. Then we used Composure package and patch package because we had to use some software considered Abundantware and we had to fix some bugs in them. So those who don't know, those are nice, but JavaScript and PHP. Then Continuous integration was a key part of all this. It allowed us to ship changes fast, run tests under code, and if the test didn't fail, we could deploy the production. And this has made breaking production very hard. But still, we managed to do it just yesterday. Some of our stack listed right here. This is not even half, but yeah. So now we get to do interesting part. As I said, I have a lot of content, so I had to do this. Only Vince gets this, but that's not my problem. So I had to cut a lot of content out and I prioritized the most interesting ones so you can go and leave and see Elisa at the end of my talk if you want what I appreciate if you stay here until the end. Well, I'm going to start with church engines and social medias. And as you may know, the loading problem is the most common one in single-page applications when they're shared to Facebook for the first time. The clients paste their URL to Facebook and sees nothing but loading in the embed. So how do we avoid that? And so as you see here in the background, very dim, but it is there. That's the whole HTML response of the single-page application that we have. It's less than 80 lines and doesn't contain any meta tags at all, which is unacceptable. So we used Puppeteer to fix this. So Puppeteer is a Node.js library for Google Chrome. It allows us to simulate user interaction and run code. On addition to that, we can just render the whole application in a browser that behaves like the normal Google Chrome. So the basic idea is to run a Node server that runs Puppeteer and route to that server from nginx. It can be done for our users. Users that aren't just looked in or just for bots and we did the bots because it's easiest one to implement. The other options kind of require you to code the same way that you would render react traditionally on the server, which is a bit different. So to show you, this is just basic nginx configuration. We have a pre-render block of the configuration. I don't know if that's the correct term. I'm not an nginx expert, but I did the config. So we checked a user agent with regular expression, but these, and if it matches, we simply route them to the pre-render service, which is basically Puppeteer server. At this point, the Puppeteer server takes control and just a tiny bit of heads up. We did things a bit differently for Puppeteer. So we add headless to parameter to the URL when we render the first application first time with Puppeteer. So some tweaks happened. It's a new site. There's a lot of ads, and ads make the page slower. So we remove the ads to give you an example. Don't use that to access the site without ads, please. So yeah, so obviously there's this one problem with it, the same one that Facebook and Google and others have, that they don't know when the page is ready to be rendered like, or taken, or snapshotting, or whatever. But it's pretty easy to do once you're in control of the application. So we simply defined this function that checks the global in window called ready to render that when it equals to true, it completes a promise. So this is basically a promise. If you don't know what's promise is, sorry. I'm not going to explain. I don't have time. So we basically wait until the app says ready, and when it does, we extract HTTP status code from a myth attack that we have. We did that with React. It's a bit hacky, but it works. And if the request ends in a 404, we can have a 404 response instead of the 200 that would otherwise always come out of the Puppeteer server, and we just take the HTML and return it as is to the port that's requesting the content. And as a side note, it's a good idea to remove images from the loading process, because it doesn't make any sense to load images when the browser doesn't have a screen, and no one even looks at the images. The market doesn't change one bit with this, but it makes it much faster. So what's it's ready to render in window? So basically we have a Redux action that we call when the main component on the page has finished loading. And that action starts this saga. When the application is in headless mode, it does something like remove the scripts and sets the render to the true. If it's not in headless mode, it does nothing. So now we have the full content and meta tags, like here's the full response. It doesn't fit anymore. And there's meta tags that you can't see, but they're there, believe me. But yeah, there's this one minor problem that nothing actually works. And it doesn't work. I mean, everything is broken, like this should be there and it's there. So I'm going to let that sink in for a moment, just read it. So basically I had to import do not use or you will be haunted by spooky ghosts from style components. This is because style components does support server rendering, but it only exposes the API for it on actual server side. And our server rendering is done in a browser and then just return to a server. So we can't use that. So we had to abuse this little API. And yeah, then we just added it to the is headless check in set rendered. And this four lines of code in total fix all of our CSS. I don't recommend you use style components, but if you do, you can fix it like this. So just to show you, here's the pre-rended site. Let's see what kind of demo effect I get today. Nice. It works. So as you can see, there's content missing, but this is all that the bots care about the main content. So we skipped everything else to get fast page loads. Focus was just stolen. So how did we build the new side without breaking to old? As I said, we were building an MVP on top of the old theme-based site. And the old theme-based site had to work as this while we were building the new application that eventually replaced the old sites. This was surprisingly easy. I didn't expect it to be this easy. I feel almost stupid including this one. So this is a key part of a code. So every custom short code that we had that rendered HTML to the theme-based site, that was basically useless in the React application. So we changed the short codes and rendered returned JSON instead of HTML and injected a component on top of the JSON in React. It's a bit hacky, but it works. But it's going to be hacky if you're going to do single-page applications with WordPress. So I wasn't supposed to go there. So as a way of optimization and a way to make sure that we don't actually change anything inside the theme-based application, we only loaded a code that changes everything we need inside REST API requests, which is pretty easy with this. Just include it in REST API in its action. And no, not by any other. So that one bit of code here, it's missed by the highlighting, but basically every ACF image that we had was in ID form and return. So when we requested anything from the API and it contained an ACF field of an image, it only had an ID which was basically useless to us. We didn't want to make an additional request. So we changed every image inside ACF to an array. As of what's the return type in the graphical user interface was. Then when we wanted to use the same menu for both of the applications, we needed to change something which is to use this filter inside blogging and changing the query parameters inside REST API is also pretty easy. So this is Springt Mag, but it's actually the DigiMag. So we wanted to order it by the public state instead of the chronological order in WordPress. And we wanted to remove some values from it too. So this is just an overview of how you actually change the item. I'm not going to go into detail on that either, how we change the query. So basically I'm going to show you the function in a bit, but we wanted to include six weeks of next issues of the DigiMag in the results instead of from the first published one. So we did it like that. And the removal is also just replace something in the array. There's also some broken syntax highlights. Yeah, it's basically just a meta query and we have a lot of caching in front of it. So it's fine that it's a meta query. Don't judge me. So moving on to the problem that made me want to go with my career as a developer and move on to live as a sheep farmer. So this is pretty bad if low-end devices take 30 seconds to render your application. So a few months ago, we started noting that sometimes when you clicked a link in the application, it cost a full page reload instead of seamless retouching. But we dig into it a bit further and found out that it was actually transitioning, but it was triggering a reload immediately before the transition. So we tried debugging it, but we couldn't even reproduce it like every time. It was just a bit different. So the problem got worse and worse. We didn't even look at it for months. Like it didn't occur to some users at all, like me. I didn't have the problem. So finally, our thick lead isolated the problem to Google Tag Manager by simply disabling every single script on the page and just checking what causes it. And it was Google Tag Manager. And after that, it was pretty easy to fix the problem. So in this Stack Overflow answer that you don't have to read, the main catch is that if you have triggers that have set the wait for tags and check validation checkboxes, you have to uncheck them as these are unnecessary and even harmful in single-page applications. What these basically do is they abort the page transition in traditional web pages like it aborts the request to the new page and sends the data to Google and then restarts the request again. So what did this for us? It tried to abort the request, it didn't, so the page went where it went and then it reloaded the page. And if you have a Galaxy trend, you try to browse an article, you're going to have to wait 30 seconds for each of them. So that was not very nice. We also had a similar thing with Frosmo as well and turns out that trackers don't work really well in case of single-page applications. Frosmo has fixed it now, but if you have to use trackers, consider again. So moving on to cache invalidation, the hard parts of this presentation. And I'm going to preface this with that this was a major fail. So, slide the easy ones first. For any front-end assets that you have, CSS, JavaScript, SVGs, use the file name with an MD5 hash in it like dog.md5hash.jbeg. The next one, I don't know, turns out these easy things aren't so popular. They don't exist. So moving on to the hard parts. So, we use a lot of WordPress transients. So, I built a better class-based API. I'm going to go over the strike-throughs in a moment. So basically, yeah, it's a class-based API instead of the Procedural standard API. It uses it in the background, but yeah. The main feature in it is the predictable transient names. It had prefetch and a list of transients with meta and an inception mode, which lets you put transients within transients. But these were removed for technical reasons. I'm going to tell about them in a moment. But then, one thing that uses transients a lot is the cache-proxy endpoint that I did. It's for third-party or native WordPress endpoints. So, say, post endpoint. We can't easily transientify that. But if you're relative to the cache-proxy, yeah, no problem. On top of that, I added auto-magical transients for all custom API endpoints. It's just one line of code if you want to use transients in it or not. And magic needs some ugly things behind it. So, yeah, as I said, unfortunately, because we're going to only run memcache, the small solution didn't run so very well. So, I didn't know that memcache maximum value size is one megabyte. And the list of my transients quickly got to four megabytes, which says to a few thousand transients in it. So, I tried storing it in WB options inside the WordPress database, but, yeah. My escuel didn't like it. I don't know why I'm not human today. But BHP could handle the value just fine. But production crashed a few times as a result of my smart code. So, we simplified it a bit, left out the list, refetched an inception mode. But a system like this could work just wonderfully with readers, or any least, recently used cache without sensible value limits. And my remote is broke. This is my demo effect today. It works. Yeah. So, I don't see my own presentation anymore, but I don't need the notes. It's just that I'm switching contacts like statement in time, so I really need the notes, but who cares? So, we run it over a weekend. It didn't go with the four megabytes, and we pretty much populated all the transients that we ever would. But, yeah, my escuel choked on the transient list. So, we didn't have the possibility to use it. But, if you use readers, readers, how do you pronounce it? I don't care. Yeah, you could use this. So, I'm going to show you some code. This is the disclaimer. I hacked it together from the parts of code that I wrote and parts of the code that are now in production. So, no guarantees whatsoever. So, look into it. And I will publish a dual-kit blog containing similar features than these. But, I have to have time first. Maybe it's done next week. Maybe it isn't. Yeah. Just to show you how. These aren't really complicated things. It's just different. So, here's the Transientify class constructor. It's, I'm not going to go into detail. Well, basically, it sets a key that's the base of all transient keys. So, we use, we have a custom access level system. So, we embed data from that to the key. So, we can have different transients for different access levels. And these are just functions that really shouldn't get anything from WB options but they do. And here's the partial options of the class. So, basically, you can set a different expiry or different permissions for bypassing transients. So, basically, every editor that we have has to get the latest content instead of the class content. You can go into detail. Prefetching is also pretty easy. You just maintain the list and then you call it in with a cron job and you loop the list. So, the interesting part of this is this get function. So, basically, the get function doesn't do anything with get data in callable here unless the transient is actually missing or invalid. So, if there's no transient, it will call get data and it's up to get data to set the transient with the set function. As you see, it's past this. So, it has access to the same transient, for instance. And this is just the magic behind it. It's rather simple code, but don't do this. And yeah, it's just a cron job that triggers the prefetching. So, to show you the category endpoints, costume endpoints that we have, this is our category endpoint that I built. So, yeah, you just define the endpoints in the construction and you define an expiry for it. This is the transient length and this is the actual API endpoint. It's pretty simple, but under these is like 2,000 lines of code. So, transients are really required in this case. To show you the base class under the costume rest route that we have, it's rather simple. It just extends WP rest controller. Yeah, you give it the namespace and a route and that's it. Then you just create endpoint in your child class to create your endpoints. And yeah, this is just so complex that I don't have the time to explain it. So, actually 10, 35. Yeah, so I'm not gonna explain it. It's there, take a picture if you want. I'm gonna put my slides online today. It's there. I'm in a bit of a hurry here. So, transient cleaner. It's just basically hooks on the filters or actions and if it loops things, if it matches transient, it will clear it. This is really just an ugly class that does nothing nice. Don't think of me as a good coder, especially Jacob. So, I don't think I have time for this. Basically, every request that we make from the client side to WordPress is cash to local forex which uses indexed database behind it. This makes application faster over time and allows for offline use. We actually shipped offline feature yesterday but we had to roll back it for other unrelated issues. So, if it were to make changes to the data that we have stored on the user's device and we changed the components, everything will break. So, we have to do something to make the local database clearable. So, we used versions. Basically, these are just objects that contain some properties like the version and these are compared with, yeah, these are compared with future classes that I'm going to show, but yeah, you can use a version to clear all data from the user's device. So, we simply create local for each source out of wrapper class around it. Add some functionality. So, we used hashes for the keys to save a bit on user disk space. We compare the version of the cache item with the application version and if it's too late, like, oh, it has been, it said that it should clear. We will nuke the cache and that's it. And yeah, it's a rather simple code too. So, we can hook this up to the data retrieval function that we have. We have a WB client, JavaScript class as well. I'm not gonna show that, but we have that. So, we have hooked into the get method of it. So, all of it, if data is going to be cached automatically, but it has a problem, like it's going to keep growing forever. So, let's build in least recently used cache for the front end based on the disk storage class. So, we have a list of all the items in the cache it can contain a thousand items and if it goes over that it will clean 10% automatically. Then, that's just basic. So, we override the get method inside disk storage to set the order every time a request is made and accessed from the cache. It moves upwards in the list so it doesn't get removed and it just uses the disk storage get behind of it. This is just the cleaning method. It's not the manual, but this works. So, on top of that, I'm gonna have to wrap up this one, I think. Yeah, so, dealing with fragmented data, which made me regret not using TypeScript. Well, basically, there's many different formats or at least two in this case for seemingly the same data. Like, this is a response from a WordPress native endpoint of an image featured image to be stacked and this is an ACF field image inside an REST API response. So, as you can see, they're a bit different contain the same data, usually. So, this is the case for everything inside the ACF fields that we have from this plugin and other things as well, but for the sake of simplicity. So, we want to use the same components, regardless of if it's ACF image or featured image. So, we used models. This is basically just that JavaScript object contains three properties at all times and the components can rely on them so they don't have, why am I moving this? So, they don't have to do this kind of ugly thing inside the component and don't ask why ACF image is always an image.cuba. That's just the five year old thing that we couldn't change. So, okay, I have time for this then. So, authentication in English is both with WordPress. The obvious thing to use was cookies. Like, that was the easiest one. Anyone that's made a REST API request from the WP admin has wondered why it's so easy. Probably, I did. But with cookies, you have to use nonsense with important requests and you can't do that or you can, I did and when I run into trouble, I want to ask for help and this is what Ryan McHugh told me. So, basically he said fundamentally, cookie authentication is only meant to be used inside logins or themes and if you're not using them, you shouldn't do it. Just ignore the rest of what he said. I don't like the idea of creating a single page application that's boosted up by WordPress. So, moving on to other options, JSON web tokens we used briefly, but they're meant for server to server applications and not really client side applications. So, all of it was, we used this commercial plugin called WP or server which is in this address. I don't really recommend it, it was horrid, but it did the job. So, this is how I set it up. Yeah, so the doc suggests that you do this, even though this is from the user credit and source grant type documentation. You might see something wrong with this. So, base 64 isn't an encryption, it's just an encoding. So, if you were to embed this client side, we would have some troubles later on. So, what could possibly go wrong with this one? Everything. So, let's do something else. So, instead of finding the documentation blindly, we did that, but with our REST API endpoint. So, we just post the username and password at the header and forward the request to the OAuth server and return it to the client normally. So, I'm gonna have to wrap up now, but this is how we did the authentication. I spent like two months getting it done. So, I don't really recommend the plugin, but I don't know what your options are. So, I'm gonna have to skip these. Yeah, thank you. It's been a pleasure, but too much content I know. So, to send you down some rabbit holes, here's the reasoning behind the nonces from REST API. Take a picture if you want. And here's the property start article. We just did this one and added some boilerplate and put it in production. It has worked tremendously well. I can just recommend it, just do it. And this was the Google DacManager answer. And yeah, I've been, kiss only. Thank you. Thank you. So, we have about five minutes for questions. So, if we have some audience questions, please raise your hands and then there will be a mic given to you. So, there's one in the back corner. Hey. This was a fully react front end, right? Yeah. Is there any reason why you chose Puppet here over, for example, Next.js or something like that, which actually provides all of the server rendering? Yeah, basically. Did your course then continue or did I interrupt you? So, we didn't want to use Next.js because it's react, but it's different. You can't use same kind of things like you can't use react router with Next.js. And we wanted to use react router. You just wanted to use it. And we wanted to try Puppet.js. I see, because you have in Next, you have the possibility to do server-side rendering and every concurrent request after the initial request is done in the client, right? I'm not sure if I heard that. In Next, you can actually do both things. You can do server-side rendering. Yeah. On the initial page. Yeah, I've used Next.js briefly. I liked it a bit, but we just didn't go with it. No other reasons at any other. Thank you. Yeah, other questions? There's one over there. When building this, did you look at GraphQL over using a REST API? I wish I used GraphQL, but we didn't have the expertise. No one know how to use it, but everyone wanted to use it, but we didn't know how to set it up. I've heard Morgan, I think, has set it up. I'm not sure. Maybe I heard it. Maybe someone from them is going to talk about it sometime. Hi, I'm Christopher. Did you notice a change in SEO performance before and after the design? Yes, it went down. Well, it's been temporary. We had some problems with the property, obviously. But yeah, it's going back. I don't know actually why it's going down, but maybe it's because it wasn't able to find sitemaps in the beginning and that caused a bit of trouble. But every new article that we publish is there for within like 15 minutes from publishing if you find with the title to put it to Google, it's there. So it should be okay, but definitely metrics are down. Any more questions? There was someone already mentioned next, J.S. Did you ever consider Gatsby? Isn't that more like, I don't know what to call it? It's a basic. Yeah, so it's more like a generator. Yes, but it's basically a... Yeah, well, yeah. So I hadn't heard of Gatsby before we started this. I guess it would have like built-in direct WordPress support with the GraphQL. Yeah, so it sounds nice. So maybe I'll consider that in the future. Yeah, most of this stuff would have been solved just like that. Yeah, hey, thanks for telling me that now and not a year ago. I said that I was going to build this, so why didn't no one tell me what to do? Like, I had no idea what to do, but we did it. Yeah, we have one more question on the back there. Was there a reason why you wanted to use the build in WordPress REST API endpoints because I've found it easier to use a transform function to put the data in the format I want to use instead of just using whatever WordPress spits out? Yeah, mainly it was an MVP. That was the reason and we kind of had to stick with it. It's going to improve in the future, I'm sure of that, but as a minor announcement, this is my last day at Binsett. So I don't know how it's going to evolve at this point. OK, I think we're out of time for questions. So yeah, once more, let's give a hand to Christian.