This workshop provides an introduction to working with content in TypeScript using CMS. It covers topics such as generating TypeScript models, implementing Next.js, resolving rich text fields, and improving type safety and code maintainability. The workshop also discusses importing content packages, troubleshooting, and automatic model generation. It emphasizes the importance of generating strongly typed models for content and demonstrates how to resolve rich text elements into React components.
1. Introduction to CMS and Workshop Overview
Hello everyone! I'm Andrey, a developer evangelist for Contentby. Today, I'll show you how to work with content in TypeScript using CMS. We'll cover topics like Strongly Attacked Models, generating content models in TypeScript, implementing Next.js, resolving rich text fields, and more. We'll also discuss deployment pipelines and content verification. If you're interested, let's get started! You can either watch or join the hands-on workshop. I'll provide a link to a repository you can clone, and I'll guide you through the implementation. No registration is required, but if you want to use the headless CMS, there's a free trial available. Let's dive in!
So, hello, everyone, once again. I hope that the majority of attendees who want to join here today are here. Let me first introduce myself and I hope that you can all see my screen, so I'll show you the second slide of the presentation where I'll outline what we're going to go through today. I'm Andrey, I work as a developer evangelist for Contentby. I'm going to show you a bit of CMS. I want to emphasize that I'm not doing a product pitch today, I'm just trying to show you how we can work with content in TypeScript, and hopefully the principles that I'll show you are applicable to other CMSs, provided that they have similar tools, which I hope they do.
I used to work for a customer success department for quite some time, so I think that I have an experience with how people are struggling with what happens when their project grows, and when their content models change. I don't know for all developers, but I would say for most of us. You know that when you're setting up your models, your databases, everything at the start of your project, they never stay the same. You always end up changing a lot of things, sometimes even replatforming your whole project, so I'll show you how the Strongly Attacked Models can help you make that more seamless process. It will never be seamless, but we can at least try to be optimistic about the change.
Alright, so let me switch here. Today, the workshop is estimated to be three hours. I will try to be a bit faster than that. We'll see how it goes. What I wanna do at the start is we actually have an import package that I'm gonna show you how to import it into a new project. Then I'm gonna show you how we can generate the content model in code, in TypeScript. So, I'm gonna show you the content in the CMS. I'm gonna show you the Next.js implementation. I've prepared a bundle for you that you can get from clone, from GitHub. We're gonna generate the TypeScript models. And then we're gonna implement one additional page in the Next.js implementation that will fetch content, stronger type content. And we're gonna resolve rich text fields into React components. So, we're gonna keep the stronger type models with the content. And we're gonna resolve even rich text so that marketers can work on their landing pages without your help, and you can keep having strong types in your implementation. At the end, we can also discuss the deployment pipelines and content verification discussion. This is based on our own experience when we've had a lot of issues and inconsistencies between website implementations and what was actually in the CMS. And when the CMS content model changes, your website and your website is not prepared for the change. It can end up with a nasty error. So I can tell you what we are currently working on, and what are some of the ways how we can avoid these errors. Cool.
So, if this sounds good, we can move to the first step of the workshop. Now, I aim to do this as a hands-on workshop, so you can, if you have a Visual Studio and an Internet connection, that's all you really need. But if you just want to watch, feel free to do that. If you want to use my project from the content, from the headless CMS, that's also possible. I'll share the project ID with you, so you don't have to work with content. You can stick to your Visual Studio code and work with the content there. So no need to register. If you don't want that, if you do, you can do that too. So this is a link for a repository that you can clone. Let me actually go there for a second. It should be public, so you should be able to access it. The only thing you need to do is do a git clone in this repository. Maybe someone can put it in the chat, or I can do that? Yes. Oh, thank you. Thank you very much. So feel free to clone that. I'm going to go through the implementation in a second. The only thing I wanted to tell you is that this is a Next.js-based site. Next.js is the most used framework for most of our clients. It gives you a lot of flexibility and lots of new features while keeping the implementation maintainable. We use it on our own site, so it's the easiest stepping stone for me as well. So when you clone this, we can take a look at it in VS Code. I'm going to open that here. I will make it bigger a bit. Let me know if you can read it okay. Otherwise I can make it even bigger. When you download or clone the project, there is one important thing, and that is the content workshop import package. If you don't want to register for content, and just want to use My Project, that's fine. If you do want to register for content, then there is a trial. If you go to content.ai. Here you can get a free developer plan. There is a link for a sign-up page. You can sign up for the trial. And I'm going to show you how to import content into a blank project. If you don't want to register, that's fine. I'm going to do that anyway. And I'm going to share the ID with you. Actually I think the project ID is here in the readme of the repo. This is the only thing you'll need for the workshop in terms of the headless CMS.
2. Importing Content Package and Verifying Content
To import the content package into your project, go to the content template manager and follow the steps to import the package. Make sure to generate the management key in the project settings and enable the management API. Drag and drop the package into the content template manager and check the checkbox to publish language variants after import. Once the import is finished, you will have all the content in the project. You can verify the content by accessing the deliver.content.ai website and entering the project ID. Additionally, you can use the project ID to access the content items in JSON format. If you need the template manager, you can find it by googling 'content template manager' or visiting kentico.github.io/content-template-manager.
Now, let me just switch to the right project. So, when you register for content and you create a new project, you're going to see this. So this is an empty screen. There is no content here. Nothing. It's completely a blank project. And this is the ID that I'm going to be using. Yeah, you see that it's 02A. It's the same one as is here. 02A, right. So, whatever I'll do in, in the project, you can consume it using the project ID.
Now, the first thing I want to do is I want to import the content package, right? So, there is a content package content workshop import package.zip. This is something that I want to import into my content project. Now, to, oops, create a new tab. Perfect. Now, for content, there is a thing called content template manager that allows you to kind of do a backup of your project, right? You can export import, you can even, there are templates that you can use. Now, when I prepared the project, I did an export. Now, I'm going to do an import, right? The target project ID is the one that I showed you. Now, for a second, I'm just going to go into project settings and generate the management key. So, if you want to import into your own project, you're going to need to do the same, to go into project settings, API keys, and enable the management API. So, let me activate this and copy the key. So, as the management API is available to all plans, even the free developer one, yeah? And I'm going to put it here. And the last thing I need to do is actually drag and drop the package. So let me just find the right folder. That will be......this one. So here, you see that this is the Content Workshop import package, so I'm just going to drag this here. And I want to publish language variants after import, so if you're importing into your project, then do the same, just drag and drop it here. Do a project ID, content management API key, check this checkbox and prepare for import. But there shouldn't be any errors, so, there our data inconsistencies are 0, so you get a nice status report. There are no issues, so I can proceed with import of data. So this is going to take a second, but once this is finished, we will have all the content in the project, right? So you see data was successfully imported, so I'm going to close this. Oops, did not want to do that. Let me see. So this is the project. Let's switch back to content. You see now I have three content items here and I should also have some assets here, right? So we have three images, three content items, so everything is as it should be. Now, if you're wondering how are we going to get the content and you now have the project ID, right? Again, the project ID is in the README of the repository that was shared, right? This part here. Now what you can do is you can verify that the content is making it through to the CDN, right? So if you go to deliver.content.ai and put there the ID of the project, so deliver.content.ai slash the project ID and slash items, you should get a nice JSON corresponding to the content items that we currently have in the project, right? Now it's probably not going to tell you much, but you see that there is a HawkIce Rock item. There's a market responding page. So pretty much all the items that we currently have in the system. So this is the first thing that you can verify if you can access this. Brittany, if you're looking for the template manager, I'll try to get there. But what you can do is just Google for content template manager. It's going to be the first link. Otherwise, it's on kentico.github.io slash content template manager. Now what they're going to see is this screen. Now I import it into my project. For that you're going to need a project ID and the management API key. If you don't have those, that's fine. You can just use mine. The project that I already imported. So that's perfectly fine. We're not going to do much in the CMS anyway. I've already prepared the content. So feel free to use the project ID that's in the GitHub report. It's perfectly fine. Perfect. And of course, if you want to import, then we can give you a few moments. That's fine. So what do you see in the CMS is there are three items here. But let's switch back to Content Model. Now, I want to show you the site structure. In the site, you currently have three content types. The root content type, I would call it, is called the Landing Page. And the Landing Page is something that is prepared for a content editor, a marketer, you could say. It has every page that is based on this content type. We'll have a title. It will have a content and URL slag, yeah? URL slag so that we know where to place that content. Title so that we name the page somehow. And rich text will be a place where marketers can create their components, where they can create the design of the page.
3. Components in Marketer's Landing Page
Let me show you the components used in the Marketer's Landing Page. There are three components: CTA, Hero, and Text with Image. Each component has specific fields and requirements. The CTA represents a button, the Hero is an image component, and the Text with Image allows users to add a headline, text, and image. These components are used within the scope of the landing page and can be reused for other pages as well.
Let me show you that in the marketers Landing Page. So this is a content that's already published. It's published, so we can see it on the CDN and you see that the title of this page is Marketer's Landing Page. The content is actually composed of a few components. So you see there is a component Hero, there's the component Text With Image, and the CTA Visit Ukraine's Travel website. And the URL slug is called Marketer's Landing Page. So it's as simple as that.
The components are based on other content types that I created. You see, I have currently three components that the marketers can use on their landing pages. The obvious one is the CTA. CTA just represents a button. It has a headline, subheadline, button text and a button link, all of which are required. Right? We want all the fields to be filled. Another one is a Hero component. A Hero component is just the main component of any image. It usually contains an image, which is an asset here. It's required and limited to exactly one asset. There is a headline, subheadline, call to action text and call to action link, which is again required and limited to a specific pattern, which is defined as a web URL. Right? So it's kind of a kind of a fixed content implementation, I would say. The last one is text with image, just another component where people can put a headline, text and an image. Nothing more complicated here. We're going to use all these components in a moment when we do the rich text resolution. Right now, we can default to landing page.
And when we actually go back into the CDN, where I showed you if you go to delivery.content.ai slash the project ID slash items, you should already see those items here. This is one landing page, HOXIROCK. There is another landing page, Marketers Landing Page, and so on. Now, if you noticed of these pages, there are two landing pages and one CTA component. That's because the components used for the page, they live actually in the scope of this page. The hero, the text of the image are limited only to this page, that's why they don't appear in the content repository while the component CTA, this one is reusable, so more pages can actually share the same content item. So that's about it with content I think.
4. Importing Content and Next.js Implementation
Let's go back into code and discuss the Content Management API key. To import your own project, go to Project Settings and API Keys, activate the management API key, and copy it for the import. The Next.js implementation uses the default boilerplate, so you just need to install the node modules and run 'npm run dev'. Create a .env file with the content project ID and ensure the website is working on localhost. The index page uses get static props to generate the pages statically. The content service is a singleton used for getting content from the headless CMS. It creates the delivery client with configuration options and is used on every page as a singleton instance.
Now I guess we can go back into code. Let me see the questions. What did we put in the Content Management API key when we imported the zip file? Yes. So if you want to import your own project, you can go into Project Settings and API Keys, and there is a management API car. What you need to do is there is a switch here or a toggle that you need to activate, and then you're going to see this key here that you can just copy, all right? Then you use that in that input and just proceed with the import and everything should go smoothly.
Sure, no worries. Now let's go back to code. I'll go a bit slower as some people are still importing. So this is... Let me see if I can move this zoom panel somewhere else. Okay, cool. Perfect. So let me close this. Now this is a next JS implementation. You see there are some basics like package.json, ts-config, some other stuff. I see we don't even have a next.js.config here. So we're using the very default version of the boilerplate. I'm not going to go through the config files. It should all just work if you just go npm i to install the node modules and do npm run dev it should all work well. Now I did the npm i before, as with another workshop, it took npm 20 minutes to actually get all the packages. So I wasn't taking any chances today. Let me check in.env, yes, this is 028. This is the.enf file you're going to need to create, right? If you just cloned the repo you're going to need to create a.enf file and put there the content project ID. It's in the readme, so you can just go into the readme. This is actually a different one, but you can just go to readme and copy and paste it into the.enf file. So it should be good. To clone in three seconds, yeah, your connection is very good, I would say. I'm on Wi-Fi, so maybe that's the problem. Perfect. So when you have the content project ID in place and we have the content there, it's already published, the website should now be working. Let me check. So you go to localhost 3000. And if you see this, then it shows that the page is... The website is working. So you should be good if you see this thing.
Taking a long time for import. Yes, that's correct. Yeah. Project ID, content Manager API, and the zip package. Yes, that's all. Sometimes it takes a bit, but it's a fairly small project. So it shouldn't be too long. Perfect. Now if you're looking at the same thing as I am. That's great. And that's all what we expect right now. We don't have these pages implemented yet. So we should get a 404 for this page. So this is expected. But we should see the two marketing landing pages and their names with links in the index page. So let's take a look at how that works. So in the index page, under pages and index, there is just a simple implementation of get static props. So in Next.js for every page, if you want it to be generated statically, you need to provide get static props and potentially get static paths methods. The get static props is actually executed during build and is used to get data from an external source. Of course, not just get data, but we can omit that for now. You see that here in get static props, what I'm actually doing is getting data from content and returning it as a prop called pages. Then in the actual implementation of the page, I'm consuming pages here and providing a structure of the website. So here, it's a very easy main structure with just a bunch of divs. There is the pages. There is a foreach cycle that converts the pages into a simple list item elements and links to specific pages. What I want to explain a bit further is the content service here. Now in the get static props, what we want to do is get content from the headless CMS and render it right, or process it somehow. Here we're using content service, it's a singleton for the whole next.js implementation, for one specific reason. If you open the services and content service, you're going to see the implementation where we actually create the delivery client as is used in the SDK. Why we're doing this is because there can potentially be many pages in your implementation and creating the delivery client on every single page would be a tedious task because there can be some configuration options, right? Here I have just project id and property names resolver but you can define some additional things here like security tokens, http client. You can define, if you want a preview, you can define preview tokens, many other things. That's why we do the configuration in the single place in scope of this content service and we're using it on every page as a singleton. So we do just one instance and we use the same instance on all the pages. So that's why we have your content service dot instance and the delivery client is actually coming from the SDK. So this is already the SDK where we are working with.
5. Generating TypeScript Models
Now let's move on to components. We have three components: CTA, hero, nav, and text-width image. They correspond to the components in the CMS and use the same content structure. The CTA component expects the content item from the SDK, with element and system properties. The implementation is just HTML, and the fields are rendered using elements dot value. The same implementation is in the hero and nav components, with different HTML structures. Next, we need to generate TypeScript models for the project using the content model generator tool. It generates models and code names for the project structure, eliminating the need for string literals. The tool is already installed if you cloned the repository. For new projects, you need to install it. There are two ways to run the tool: using a script or generating the models in code. We recommend deleting old models before generating new ones. To run the tool, create a script in package.json and provide the project ID. Run 'npm run generate' to start the model generator. The tool connects to the CDN, checks the project structure, and generates the models.
6. Generating TypeScript Models
We have generated TypeScript files for the CTA, Hero, and Text with Image components, which correspond to the content types in the CMS. These types are easily serializable and work seamlessly with all frameworks. The project file, underscore project TS, contains the configuration and settings of the project, including the content type and element codenames. This greatly helps with maintainability and allows easy identification of content types used in pages or components. To organize the project, we delete unnecessary files and use the generate models script, which loads the project ID from the environment file and clears the models folder before calling the content generator. The script configures the generation of content types and suffixes each model with 'model' for easy identification. This script ensures that the generated models stay up to date and helps with maintainability.
So, you should have, if you're using the same project ID or you imported the package that I provided, you should see the exact same numbers. And you see that this actually corresponds to the content model as I showed you before. So we have a CTA Hero and Text to Image components. And we have one landing page. All were generated as TypeScript files. So we can take a look at the CTA, for example, as we talked about that before. And you see here that it has the exported type called componentCTA. And it defines that this is a type that needs to have a headline, button text, sub headline, and button link. So it actually checks your types or your content types in the CMS, converts them into TypeScript properties and types, and generates it in the files. So you see, we have hero here. We have text with image. And we also have some words on the landing page. These are types, not classes, because types are easily serializable. If you generate the classes, Next.js would actually have a lot of problems with serialization. That's why these are types. So that it works seamlessly with all the frameworks. So this is the first thing it does. It generates the types here. The second thing it does, it also generates a special file called underscore project TS. And here you will have all the basic, you know, configuration and settings of your project. So you see that we have a one language that is called the default. It has a codename default called default project language. This is if you're creating an empty project, this is the default that you will have there. And it also generates the project structure and starting with content types, you see it has a component CTA, so that's the content type CTA. And it gives you the codename of the content type and also gives you the codenames of all the elements. This is very useful as when you're using in the implementation, when you're getting data, if someone goes into CMS and changes the codename or somehow works with the content type, adds a new one, removes an old one, or removes an element, adds an element, whatever, your implementation may break because if you're using, let's look at the page index. So here you see, we're actually using a type landing page. So that's a code name of the content type. Now, if that changes, the implementation of this page is going to break because you will not get any content anymore because the codename is not correct. The same goes for the element parameters. Now, of course, if your site is small, you can keep watching for these changes or you can forbid anyone from ever changing those, but then of course, if you personally want to change it, imagine how it would be looking for all these occurrences where landing page actually is. And if you have, let's say, like we have right now three content types, right? But in a production project you can easily go up to 100 content types, then it's going to be a nightmare to find anything, right? So that's why the project file helps a lot because now you don't need to put the landing page, you don't need to hard-code it there because you already have it defined here. So it's easy to also find where each, it's also easy to go the other way around, like if you want to delete a content item or you want to delete a content type, you can easily find in your implementation if there are any pages or components that actually use this content type, right? So this actually greatly helps with that and helps a lot with maintainability. But right now we actually have everything in the root of the project, which is not very nice. So let me delete these. So it's five files, hope I'm not forgetting anything. Yes. I'm also going to delete the generate script from the scripts in package.json as we're not gonna need it anymore. But what I wanna show you is that in the scripts folder, there is a file called generate models ts-backup. This is something that I took from one of our production projects. Now let me uncomment this. Oops, somehow my shortcut is not working. All right, maybe I need to rename it first. So let me rename it to generate-models.ts. Okay, this works. Now I can uncomment it and save it. So I'll give you a second to do the same and I'll explain what this script does. We're also going to need to add, I think we're going to need to add the script to package.json, but we'll do that in a moment. So if you uncommented this and renamed it, what this does is, first, it actually loads the project ID from the environment file. If you have the environment file in here, which you should have, then it's going to load the content project ID from there, so we're not going to have to copy it here anymore. And what it does, this is a helper function that is used to solve just some edge cases, so no need to worry about that, but what it does here in runAsync is, it first reads a current directory. So, the idea is that we want the models to be generated in a specific folder, like models. And when we do that, we first, when we generate the models, we first want to clear that folder so that we don't have any, like if we delete the content type, we're going to remove the file as well. So this script first removes all the files in the specific directory. Then when that is finished, it calls the content generator. That's this part. So that's the other way of calling it from a file. And it provides the project ID. That's this part from the environment file. And configures how we want the content types to be generated. So we don't want any timestamp to be included because if you include timestamp, it's gonna render a code change every time you do the generation. So you don't want to clutter your GitHub pull requests. So the timestamp is removed. And it has element resolvers, file resolvers, content type resolvers, in case you want to use a different casing than is the default. You see, I'm using a Pascal case for most of these. And there is again the helper method that solves the edge case when your content type code name starts with underscore, I think. But otherwise we're just suffixing every content type with model so that we know how to distinguish the generated models from anything else that is in the project. All right, so we have the name of the content type plus the model suffix. And then this is only a piece of code that actually runs the thing. So what I'm gonna do is I'm gonna create a script in package.json.
7. Generating Strongly Typed Models for Content
To generate strongly typed models for your content, follow these steps: Change the directory to 'models' and run 'TS node' with the 'generate models.ts' script. This will create the models folder and generate the necessary models for your project. If you make changes to the CMS, you will need to regenerate the models. Add the script to your package.json file and run 'npm run generate models' in the models directory. This will ensure that your models stay up to date. Remember, you only need to run this script if you're adding or changing content types in the CMS. You can now import the models into your code and use them to fetch content from the CMS. Make sure to define the strong types for your content items and import the corresponding models. This will provide you with strongly typed responses from the content SDK. With the strongly typed models in place, you can easily work with your content in a type-safe manner.
So feel free to do the same. Let's call it generate models. And we're gonna first change the directory to models. And then I think, let me check, but I think we're gonna need to run this with a TS. Let me see. So the first time I'm cheating today. Yeah, we need to run TS node. So TS node. And because we are in the models folder at this point, so we need to go one level up, go to scripts, and generate under our dash models that's TS. So make sure that the CD models is there, because otherwise, if you just invoke the generate models, it's going to wipe your repository. Right. So be careful about that. There is code that deletes stuff. So we're gonna go into the models folder. And of course, we need to create that models folder, because right now, we don't have any. So I'm going to create the folder here models. So that we have something where we can go and then run the script. Perfect. So npm run generate models. I hope I did not forget about anything, but it seems it works. So we're looking at exactly the same message from the model generator. It again found four types found one language found zero taxonomies. And it's using three custom resolvers for file names, content elements and content types. That's expected. And when we look at the models, you see there is the project file that contains the Generated Project Structure. And we have the component CTA model, component hero model, component Text with Image model and Landing Page model. So exactly what I was after. And when you look inside, it looks pretty much the same as before. But the time stamp isn't there, but again, just the types. And that's all we need for our project currently. So now we have the strongly typed models for our content. Now if you change something on the CMS side, you obviously need to regenerate this. So this is not an automatic. It's it's something that you need to run every time you change something. And of course, if you remove a content type, it's gonna delete it from here. I'll give you a second to actually do that. Let me know if you have any issues or if you need me to repeat something. So this is the script that you need to add into package.json. The name is up to you. I use the generate models, but first we need to go into the models folder and then we need to run TS node which should already be installed. I think we have it here at TS node and run the scripts generate models.ts. Now this is something that we're going to need for further code so let me know if it worked for you. Brittany, yes. Exactly, yeah. So Brittany asks if you need to run this only in case when you do a change to the content model not when you're adding new content items. Of course, we're talking about templates for content, right? So if you're just adding new content that's perfectly fine. You only need to run this if you're adding content types, if you're changing them, basically if you're touching the content model. If you're not touching the content model and your marketers are only creating pages, adding components to pages and so on, no need for your input here. I'll make it a yes, the recording will be available. I'm not sure where, but I assume that the GitNation team will let you know, but it's being recorded. So you can get back to this. Yes. All right. So another, just a few more seconds so that we all are on the same place. So if you have the script here, just run the generate models, generate the models in the models directory, and we'll move forward. So if you have these four files here plus the project structure, then you're ready to go. So the first thing we can do is to kind of fill the waters with the models. You can go back into index and adjust the way how we get content here. Let me just make this smaller so we see more code. So here in the git static props, we can actually change this call so that it doesn't contain any string iterates. First of all, what we want to do is say that for items, we can define the strong type. So here I'm just going to do landing page, the landing page model, of course. So we're defining the type and I'm just going to add an import from top model's landing page model. Obviously, this is the way where we, that's the place where we generated it. So we're now getting, we're now going to get a type response from the content SDK. The same I'm going to do for the lending page type. So I showed you that we have the project structure there. So what we can do is we can just start typing project model. It's going to offer the models slash underscore project file in there, and it automatically added the import. All right, so this is what you want to import.
8. Improving Type Safety and Code Maintainability
We improved the code by replacing string literals with typed variables, which enhances type safety and allows for easy identification of elements used in the implementation. By using the landing page model type, we can ensure that the pages variable contains only landing page models, providing more specific and accurate data. This eliminates the need for generic content item types and allows for more precise dot notation when accessing elements. Additionally, if an element is removed or changed in the content type, the code will generate an error at build time, preventing potential issues in production. These improvements enhance code maintainability and reduce the risk of errors. The implementation remains the same, with the headline and two items displayed, and the 404 error still occurring when clicking on a link. If you have any questions, please let me know.
And we want to do project model content types. And we're looking for a landing page. Now, GitHub Copilot already knows that, but if you only get the InteliSense, you see that the landing page is the only thing that is available right now. So I'm gonna do landing page and code name. We're looking for a code name, right? The landing page code name was the thing that was there before. And when you look at what that is, that's the landing page here, right? So this is a string literal, but it's generated. It's not in our code. So that's the second thing that we can do. And the last thing to improve this is to adjust the elements parameter. I've seen here we have two string literals title and URL's slack. So we can adjust that with projectModel.contentTypes. We're looking for a content type landing page, right? Because we're defining that here as a type. That's the landing page. So we're doing the same here, landing page. We're starting with that. But instead of code name, we're looking for elements and we're looking for title. Oh, we're going to do the same for the URL Slack. So projectModel.contentTypes.landingPage. Elements. And we're looking for URL Slack code name. And then we can just delete this. So it's a bit longer, but you can always just, you know, put this in a special variable, somewhere on top of the API call. But the important thing is that we don't have any string literals. And when you go back into the URL Slack code name, for example, and you want to find out how many pages or how many components are actually using the landing page URL Slack, you can just click here, click Go To References. And you see, this is the only place where we are using the URL Slack. So if you want to delete something from the Content Model, you can easily check in the implementation if there is something that is actually using that element or the whole page, for example. If we go to references, this is the only place where we use that, right? So we're already getting some type safety into our calls. And of course, we're using the GetStaticProps and Props type here to transfer data between GetStaticProps and the page component. But we can make this better. Look at that here. So we have pages in the implementation, we have pages variable. That is an iContentItem, very generic item that we use when we don't know what kind of item we're gonna get. The problem with that is, if we look at the page elements, so this can be, this is iContentItem, so it can be anything. And we look at dot elements. Let me move this away and do a dot. You see, we don't get any interlist. We don't know. Or Visual Studio doesn't know what can be there or cannot be there. So we can make this better. In the props, that is an interface defined to transfer data between these two methods. We can say that the pages is not going to be content item, but it's gonna be a landing page or a landing page model, right? Because this is what we get here. Landing page model, we know that there's not gonna be anything else because we're limiting that with the type. So we know that this is gonna be a learning page model. It's actually gonna be an array of landing page models, right? Because we've got multiple pages here. And when you look at page data items, you already see in the internal instance that we have a landing page model array here because we started with types up here. So this is the only thing we can get here. So in the props, when we change it to landing page model and go back into the component on a place where we actually use the first element, you'll see that the page is not an iconten item anymore, but this is a landing page now, and the elements actually correspond to the type that you defined. So now we know that there can be content, title, or URL slug, nothing else. So when we do a dot here, we don't get any generic set of things that we can do, but we get exactly the three items that we defined in the type. So we know that the URL slug is there, and another good thing is that if you actually remove the URL slug or delete something else from the content type that you are using, let's say we're going to have a nice other property here, the moment you delete it and you regenerate the models, you will get an error here, that that second. So if your implementation expects something that isn't there, you will get an error that will fire at the build time. So you will not be able to build the site and wait for a nasty errors on production now because you won't get the error right away. Okay, yeah. Otherwise, if you just use the iContent item, it doesn't know what kind of elements are in there. So it's going to go through. But if you're using a strong types, it's not going to happen. So I'm just going to put it back into urls-log. And the same goes for title. We do have the title there and we know it's a text element. So that's another great thing. We know what kind of type we can expect here. So this is a text element, urls-log is a url-slug element. So it actually refers to the type of the specific element as well. Perfect. So now let's try to run this. So we did not change anything functional. We just changed the string literals to nice types. So we should be looking at the exact same implementation as before. Right, so we still see the headline, we see the two items here and we still get the 404 if we click on it. So let me know if there are any questions.
9. Troubleshooting and Automatic Model Generation
If you're not getting errors, check the elements parameter to ensure you're getting the data correctly. Console log the page to see the data structure. Make sure the items are strongly typed and the content type and code name are correct. There is no automatic way to call the generate script when a model is changed. Currently, the generate models script is called manually and compared to the committed models in the GitHub repo during the deployment process. Enhancements to the project structure are being worked on to include additional information not available in the delivery API response.
I'm going to give you a second to get here. Okay. Brittany, you're not seeing the page titles anymore. All right, but you're not getting errors here, right? If you're not getting errors, maybe you want to check the elements parameter, if you're actually getting the data. What you can always do is just do a console log here and look at what is in the page. Like, the first one should be enough, yeah? If you save that and refresh the page, you will see here what kind of data you're getting. So this is one of the items. So you see, in elements, we're only getting the title and URL slug. So these are the two values, and we're getting the system fields of the content type. So if you see everything here, it should be okay. You're getting undefined, okay. In that case... In that case... I would suggest to check these two things. In the items, you're strongly typing the response, so that should be the model. And check that the type is correct. If you don't have the type correct, it will not get any data. Right, so make sure that you're using the content type and a landing page and code name here. Otherwise, we're all working with the same project. So it should work. Is there an easy way to automatically call the generate script when a model is changed? A good question, not right now, because the delivery, like there are two APIs, right? The delivery API and the content management API. The thing is that there is no notification when a content model gets changed. So we cannot bind this to a webhook or anything. The way how we handle it is, if you're changing the content model, you're actually calling it manually and committing that change into your GitHub repo. If there is something that should be or could be unexpected, then we are calling the generate models in the scope of a deployment process where we actually compare if the generate models are the same as what we have committed in the GitHub repo. So that way we know, is there a change that nobody told us about? And in that case, if there is a change, then the build will fail because there is something that obviously is not there or the website is not expecting. So we're solving it this way. So we do it on every build. But yeah, right now, there is no way how to do it automatically. But what we are currently working on is enhancing the project structure. So this file to also contain other things like taxonomies, like the description of elements and so on that is not available in the delivery API response, but it's only available in the management SDK because those are kind of sensitive data of your project. So we don't have it publicly available, but if you have the management API token, you're going to be able to generate those things as well. So I hope that answers your question. Let me know if the website is working for you now and hopefully you found what was the problem. Perfect. I haven't found it. Alright. Yeah, I'll keep looking at this call. It has to be somewhere here. My bet is on this one. And maybe check that the code name of the landing page is really a landing page because that's really the only thing that we changed. Perfect.
10. Working with Components and Navigation
Now let's move on to components. We'll start with the CTA component and define the type that it expects. We'll do the same for the hero and nav components. The text with image component requires a fix for the DOM node type. Lastly, the navigation component will render a header menu listing all the components used on a page.
So when we have this ready, now you have an idea how to work with the content this way. So we can move on to components. So the components, let's start with the CTA one. So, you see that they are now relying on you to provide elements and system, but again, elements is any, system is any, so this is hardly any help, right? So what we can do is we can turn this into a React function component and define the type that this component expects here. So, let's call this one, I think this one is CTA component or component CTA. I think, yeah, it's a component CTA model. You see the import was added automatically. So now we actually have it strong with type. The elements will now get a type. So, these elements now are headline, button text, sub headline, button link. The same goes for system. Now the system is icon to an item system attributes. So, we're going to get intellisense for those as well. And that's the only thing we need to do for components really. Apart from actually checking that the case of all the elements is correct. But here you see, if we go elements, we can get the four fields that we have on the content type. So, we should be good to go. So, no additional change should be needed here. The only thing sometimes happens that if you don't have the casing defined, typically there is button underscore text, and you need to change it to button text. But again, depends on the configuration of the delivery SDK and of the model generator. So, these two should be in sync. Right now I have both set to Pascal case. So, it's working well. So, this is the only thing I need to do here. So, let's save this. I'll do the same for the hero and nav. So, I'm going to turn them all into typed components. This one is going to be component hero model. And let's add the import. Again, no additional change should be needed here. Because we have the CTA text, sub headline, headline, everything should work well. So, let's save that. Let's go back to navigation. So, navigation, actually, does not get any content type. So, let's leave that for the last. I'm just going to fix this one for now. So, the text with image is going to be, again, fc text with image, component text with image model. And again, there should be no additional change needed. There's only one thing here in the text with image and that is the DOM node because the DOM node is not defined here. The property name does not exist on type DOM node. It does exist there. But the problem is, the DOM node, when we look at it, is composed of multiple things. Comment element, node, and other stuff and we're looking only for the element. So, the reason why we're getting this error is because the name is not present on all the elements that are in the DOM node type, but only on a specific one, so, what we need to do is check the node type, to be one, and then type the DOM node as element. Then it should work. One actually comes from RFC for the DOM node. If you're interested, and you look at the definition of the get note type, there is a link for the DOM spec. You can take a look. One is actually four elements. So, this is not ideal. I assume that there is an emulation that will look nicer in the code. But this gets rid of the error for now, so, it should be good. We're gonna do the same here as element. Attribs className, and that's all. Now, of course, you can just leave it as it is. It's just a warning, yeah? It's not gonna break our implementation. But just so you know, we need to define the type to let TypeScript know, hey, we know what we're doing. We know it's gonna be an element. So, that's all we need to do here. All right, so those are the components. Last thing here is in the navigation. So this is the component navigation. This one is a bit special because it does not get a special content type. What this is gonna render, it's gonna render a header menu that lists all the components used on a page. Let me show you what I mean. I'm just gonna switch back to content for a second and show you one of the landing pages. So here you see under content we have three components. What the navigation component is doing is it's going to take all these components and it's going to generate a top menu that says the Tunnel of Love. It says the wonderful country, beautiful nature, and check out the Ukraine's nature. It's going to render that as a top menu and it's going to help your visitors navigate through the components on the page.
11. Working with Arrays in Components
The component expects an array of iContentItem, but we need to define an interface called props and call it data. This allows us to iterate over the array.
It's just a simple anchor moving on the page. There's a sharp sign and the name of the section. So what this component actually gets or wants is an array of iContentItem. Now obviously this is not what will help us because we don't have a way how to define an array on the component because it would be an unlimited length of properties. We don't want that. So we can work around this by defining an interface called props and we can call it data. It's going to be iContentItemArray and then have props here. This is going to help us in a way that we can iterate over the array.
12. Creating Code File for Landing Page
We have only one property on the component called data where we will provide the array. We can use component CTA, component hero, or component text with image model. The only property that all of these content types share is a headline. We're using the component's system code name as the ID for the anchor link and the key in the for each cycle. We can make the data optional, but it would be a mandatory component. Next, we will create a code file for the landing page, using the URL Slack to define on which URL the landing page will be rendered. We will also provide a get static paths function to define on which paths Next.js should render a page.
So we have only one property on the component called data where we will provide the array, but here the iContentItem will actually mean it's again, a generic thing. Right? So when we do the cycle here, you see that this is the only way where we have the data used. And we are doing a for each cycle here where we are actually looking at component and we're using a headline out of the elements of those components. Right? The thing is that if we use iContentItem again in elements, we're not going to get anything here because IntelliSense has no idea what kind of component can be there. So it works pretty much as any. Yeah. This is not ideal, of course. We know that we only have three components in there. So what we can do is instead of iContentItem, we can use a component CTA or component hero or component text with image model. And it's going to be an array. So we now know that there can be three components and it's going to be an array of those. So we're not saying there's going to be one hero, one CTA and one text with image, we're just saying in the scope of those components, we expect one of these types. And what will happen is in the data, we're going to get only the elements that all of these content types share. So we'll have the same. When we look here on the place where we want to use it, I think it's here. When I do a dot now, you see that the only property that all of these content types share is a headline. So I can use headline safely here because regardless of what component I have here, regardless of what component the marketer put on the page, it will always have a property called headline. You see that is the only one that all of those share, otherwise the elements can be different. So again, we're doing this so that we get an intelligence here and we get the type safety. So right now, if I put here elements sub-headline, I immediately get an error cause the property sub-headline does not exist on type, you know, blah, blah, blah. So again, you're gonna get an error at the build time. So when you look at the models again, you see component CTA has a headline. Component header has a headline and component X with each again has a headline. So we can safely use those. And the way how this works is we're actually defining the anchor link and we're using the component's system code name. System code name is something that all of the components share, regardless of the content item type, if it's a component, if it's a reusable content item, anything, it will always have a code name. So we can safely use that as the ID that we use in the link. We also use it in the cycle, in the for each cycle cause we need to provide a key. So we're gonna using the code name. We could potentially also use an ID. So ID is a another thing that we can easily use, but it doesn't matter. Code name is a bit shorter. So that's the only reason why I'm using that here, but you can also use an ID. And that concludes this component. Now this one is also strongly typed. Perfect. So let's go back to index. Let me know if you have any questions or if I'm too fast, let me know. I can explain something further. Otherwise what I wanted to show you is that now we have navigation data is now, which is not very nice. But it's the index page where we don't have any landing page data, right. We only use the index page as some kind of some kind of overview page of all the pages that are in the system. So I'm just gonna keep it like that. If you don't like the data now you can always make the data as an optional property. We can do that now as we have the strong type here we can say that the data is optional. And if the data is optional, then this is a mandatory component at that point. When we do data optional, if we leave it as it is then here we would get an undefined error. If data would be undefined we cannot obviously use the math function on undefined. So this would be then mandatory not that it would work with Null but yeah. Perfect so I think that we went through generate types with models to fetch content in Next.js and we already did the adjust components to use strong models for content. So hopefully. So good job to everyone who did that and the next thing that we're gonna do is create code file for landing page. I think that we're moving forward quite quickly so that's good. So we can spend some time in here. So let's create a code file for landing page. Now the landing pages, they feature URL Slack. Let me just make this collapsed and look at the landing page model. In the landing page model, there are three properties, content, title, and URL Slack. So we're gonna use the URL Slack to define on which URL the landing page will be rendered. So I'm gonna create a new file under pages and call that Slack in square brackets. In Next.js, the square brackets that define that this is a variable in the route. So apart from the actual page that I can actually copy from index, we will also need to provide one special function called get static paths, where we define on which paths Next.js should render a page. So let me just remove this and create another function here called get static paths. It's static paths. It's again, going to be async, so we're gonna need to get data and we need to import get static paths from Next. Now it's gonna complain because it's not assignable, void is not assignable. Yeah, we need to return some stuff. But we'll look at that in a second. The important thing is we need to return here that there are pages. Get the parts we want to pre-render based on the landing pages.
13. Generating Paths for Pre-rendering
We use the SDK to get the paths we want to pre-render based on the content in the Headless CMS. We define the type as project model, content types, landing page, and code name. We limit the API call to only the necessary data by using elements and URL Slack. We return the paths for all the landing pages and specify the slug value for each page. Finally, we set fallback false for additional features of Next.js.
Wow, GitHub Copilot actually does a better explanation than I do. So yeah, that's exactly what we want to do, get all the paths that we want to pre-render based on the content we have in the Headless CMS. So in the CMS, we now have two content items based on the landing page content type, right? So what we'll do, okay, landing pages, we're gonna again, use the same thing as we have here. Yeah, we're going to use the SDK. So landing pages, let's say landing pages paths to make it more clear. We're going to wait for the API to give us items. We're gonna use the landing page model again because we already have that in the system. We have the model there. Then we'll of course need to define a type because unfortunately we cannot infer it from the type but we need to define it as project model, content types, landing page and code name. And here we're actually only looking for paths. Next.js will not let us even transfer more data between the paths and props. It will only let us transfer the slack. In the end, it's only the variable that is in the file name of the code file. So we're gonna do the elements parameter again. We don't really need to do this but if we just omit the elements parameter, we would get all data of the landing page model. When using CDN, I know that we don't really care about the build times for small sites. It's just the right thing to do to limit the API call to only the thing that we really, really want. And you see, Copilot already knows that. So we're using project model, content types, landing page. So this is the type and we're looking for elements and URL Slack. It didn't get the casing, right, but we're looking for URL Slack because we're trying to give Next.js the paths where there will be pages. So we're looking only for URL Slack and the code name. Now we're not gonna limit it any way. So we're just gonna do to promise. And of course, for the GoodStaticPaths, we need to return props or paths. Now I'm not 100% sure. Let me check. So I always copy the implementations from one project to another. So I don't know if it's props paths or paths props. All right. So it's actually paths the other way around. So we need to return paths. And in the paths we're actually trying to return an object that contains params and slug, let's say this will be landing page, right? This is the structure or an object that we need to return. Now the slug actually corresponds to how the variable is called. So you see, in my case, it's a slog in square brackets. So we need to return this. Now we obviously need to return it for all the pages. Right, so what we can do is in the paths, we're going to take lending page paths and data items, cause this is just the response of the API, right? In data, we get other stuff like pagination linked items and so on. We don't need any of those right now. So we're going to default to items in here. You're going to get the array of learning page model. Those are the raw data that we are interested in. So we're looking for items, but we want to map those items. Let's call this lending page. We want to map that to this structure cause we need to return the params and slug. So what we're going to do is we're going to return here a structure. We're going to put their params. We're going to say slug is the learning page elements, URL slug value. Again, a nice guess by Copilot. So this is the object that we need to return. And in there, in the params property in that object in the variable slug, we want to get the data from the actual landing page. So that's why we're getting the elements, the URL slug, and we are looking for a value. So here, we're going to get the market, the funding page, the hog, I stroke and so on. And we just need to return that to next. Now it's complaining about one extra bracket. I think now it's correct. One extra bracket here. This should be fine. And there is also one other thing. And that is here. Fallback false. This is in case we're using some additional features of next. I think this is for ISR. I'm not 100% sure, I'm gonna have to check the documentation, but just default to fallback false and you'll be fine. Perfect, so this is the get static paths. That's all we need to do here. Just get the paths from the CMS and give it to the next JS. So we're giving to the next JS, the URL slugs of those two landing pages, the slash marketers landing page and slash Hawkeyes rock. That's the only magic here. Let me know if you have any questions so far.
14. Understanding getStaticProps and Path Parameters
I know that this can be a lot to grasp if you're new to next JS. Here we're just providing the paths where we want a page. Nothing more, nothing less. No data, nothing, just the paths here. And the next function that is called get static props, we have the props here but I removed it. So let's just untype it for now and get static props. What we're gonna get is... The slug that we got from the previous method. Right? So what we're gonna get is, let's say const slug. We're gonna get that from params slug to string. Or at least I hope so. We'll see in a second. So the params actually give us all the parameters of the path. Right now in the path, there can be only slug, nothing else. But if we were in a nested folder, we could potentially get multiple variables. In this case, we only have slug. So the slug to string will actually give us a nice string with the path. So it's gonna be the same as what we provided here. And based on that slug, we need to adjust the implementation. So this call, and it's actually copied from the index page. Let me just delete this because it's gonna be different now.
I know that this can be a lot to grasp if you're new to next JS. So again, here we're just providing the paths where we want a page. Nothing more, nothing less. No data, nothing, just the paths here. And the next function that is called get static props, we have the props here but I removed it. So let's just untype it for now and get static props. What we're gonna get is... The slug that we got from the previous method. Right? So what we're gonna get is, let's say const slug. We're gonna get that from params slug to string. Or at least I hope so. We'll see in a second. So the params actually give us all the parameters of the path. Right now in the path, there can be only slug, nothing else. But if we were in a nested folder, we could potentially get multiple variables. In this case, we only have slug. So the slug to string will actually give us a nice string with the path. So it's gonna be the same as what we provided here. And based on that slug, we need to adjust the implementation. So this call, and it's actually copied from the index page. Let me just delete this because it's gonna be different now.
15. Working with getStaticPaths and getStaticProps
In the getStaticPaths function, we provide all the paths where there is a page to Next.js. In the getStaticProps function, we get a Slack from Next and the data of that page, and provide it as a prop. We changed the getStaticProps to only accept the landing page model. In the final implementation, we render the title of the page and export it as the landing page. The getStaticPaths function retrieves all the paths from the CMS and limits the elements to only URL Slack.
So here, in get static paths, we were asked to provide all the paths. In get static props, we're only required to provide data for one specific page. So the get static props will be, in our case is gonna be called twice. If you have set, if you set up this on blog posts, and if you have 100 blog posts, this get static props function will be called 100 times. So for every slug, this page is, or this function is going to be called. So in here, we're gonna do an API request for items, landing page model. So again, we want a strongly typed response and we obviously want only the type landing page. Yeah, so this part stays the same as we had it before. We only want landing page models and then we can actually omit the elements parameter here because we're gonna be using all the data, all the elements, right? So we can omit that. What we're definitely going to need though is a content filter or equals filter, sorry. Equals filter. Where, what we need to do here is we need to say elements dot URL slack equals the slack. So here we are actually filtering data. We're saying that the slack that we got here, we're only looking for an item that has the URL slack set to that value. And we can make this also limited for a single item. Right? So again, we're looking for a page that has the URL slack set to the slack that we got from the paths. Obviously this is a string literal, which we don't like. What we can do here is make it a bit nicer with elements dot, let me switch that. And we can use again the project model, content types, lending page elements. And we are looking for URL Slack codename. So we can get rid of the string literal for the element. Potentially we could get rid of this one as well. In real projects, we actually have a util method that has this prefix. So we have it all in a single place. For the sake of this example, I'm just gonna leave it as it is, but just so you know that you can eliminate this overall. So again, what's happening here is we're gonna, we're getting a Slack off of the page that if we want to render. And here, we're saying we want lending page model that has the element URL Slack set to the URL Slack that we got. Right, so we're using equals filter for that. And with the limit parameter, that's actually nice to have. But this actually is very good for code readability. Because when you see a limit parameter one, it's clear from a first glance that in this code, we're getting only a single item. It's very nice if you're looking at a code of your colleagues. This is actually their way of telling, hey, this is just for one item. So this is gonna get us the data of the page. And we're gonna return props. So let me remove this. And we're gonna return here, props, and do we have props defined anywhere? We don't. So when we don't have any props defined, what we can actually do is define, I'm just gonna keep the props here. I wanted to say that you can define a landing page model here, because we know that this is the only thing that we will ever get here. The thing is, I'm not sure if we can do it this way. Let me see. So if you did the props and return the page data items, only one item, it could probably work. So if you do a landing page model here, yeah, we can do it this way. So in GetStaticProps, what we need to return is just one strongly-typed model. In this case, it's a landing page model. In props, we define, we return the result of this API call, which is page data items zero, is the first item, the only item of the API call. And in the implementation of the page, we know we're looking only for the landing page model. So we will get the page strongly-typed from the start. All right, let me know if there are any questions. And again, this can be a bit harder to understand for those of you who don't understand Next yet. So just to summarize, in getStaticPaths we're providing all the paths where there is a page to Next.js. In getStaticProps, we get a Slack from Next and we get the data of that page, and provide it as a prop. And I changed the getStaticProps to actually only accept the landing page model. And in the final implementation, this is not gonna be a homepage. Let's call this a landing page. In here, we're expecting the component to have this data, this type of data, so landing page model. And then in here we can say page.elements.title for example. Oh, I need to do a value, an export default landing page. All right, so when we have it like this, we're only rendering the title of the page and exporting the page, everything should be fine. So let's try to run this and see what kind of issues we get. So if I refresh this page, now when I click on one of these pages, they should be implemented. So let's take a look at market as landing page and we see market as landing page. Perfect. Can you show the get static paths function again? Sure, yes. So in get static paths, we're getting all the paths from the CMS. So we're getting the SDK. This is the same as what we did before. We're getting all the landing page model. So we're strongly typing the response into a landing page model. We're getting the type landing page and we're limiting the elements to only URL Slack.
16. Returning Route Parameters
This is an object that has a one property called params. And in there it expects all the params for the route. So in this case, we only have one parameter which is the Slack in the route. We are directly underneath pages and the only variable in the file name is Slack. So that's why we only have the Slack here.
As I said, this is something that you can omit, right? It doesn't necessarily need to be there. It's just saving some bandwidth. And then we return the paths where we do a 40 cycle over the returned data and we're returning the structure as Next.js wants it. So this is an object that has a one property called params. And in there it expects all the params for the route. So in this case, we only have one parameter which is the Slack in the route. We are directly underneath pages and the only variable in the file name is Slack. So that's why we only have the Slack here. Where we provide the URL Slack as we have it defined on the CMS. So this way marketer has full control over where that page will be rendered. If they, in the URL Slack in the CMS, if they put their hello world, it's gonna be rendered on slash hello world. Now we're getting the data dynamically here. So I hope that makes sense.
17. Resolving Rich Text Fields into React Components
In the get static props function, we use the SDK to get the landing page model and filter the content items based on their element values. We compare the element's codename with the URLSlag and limit the result to one item. The returned props correspond to the landing page's data items. We then copy the navigation component and assign the linked items property to an array of specific models. This ensures that only the allowed components are used in the rich text field. We resolve the rich text fields into React components by accessing the page elements and retrieving the linked items property. We encounter a problem with type assignment and reassign the array to match the expected component models. Although defining a global interface would be ideal, we temporarily leave the reassignment in place. Finally, we save the changes and preview the website.
Sure. You're welcome. And let me know if it works for you. If you're looking at this page that shows market as landing page and the other one that says hawks I rock, then this is perfect.
Can you show the get static props function? Yes. So in get static props, what kind of error are you getting? You're probably gonna get the same one. So in get static props, what kind of error are you getting? In get static props, we get the URL flag in params. And what we're doing is again, using the SDK to get landing page model. This is the same as always. And then we're using equals filter where we're saying elements. So this is important. The reason why this is elements. is because you can filter the content items based on their system values as well. So you can do a system.codename, for example, or system.language. Here you want elements. So element values. So we're comparing elements.theelement.codename. So I'm using the project model, content-dives landing page. And from that landing page, I'm using element.URLSlag, and it's codename. And of course, the second parameter is what you want to compare it with. So I want to compare it to the URLSlag that we got from next. And that is just a limit parameter and to promise and return it in the scope of props. And of course, you need to do the same thing as I did to define the lending page model for the result of GetStaticProps and for an imported data off the functional component of React. So this defines the props. Right, so this actually, the return props here actually corresponds to lending page where you say data equals the page data items zero. The page data items zero. But of course the data isn't here because we would have to go one level below. So this is what we actually do here in the return, yeah? Piper perfect, yeah, perfect, cool. So now when it's working, we can start working on the HTML structure. So when we look at the presentation again, we did this. So we did get static parts, get static props. Next up we're gonna do is resolve rich text fields into React components. So now we have all the three components or four components are now ready for a strongly typed models and we get the content in a rich text field. What do we call a rich text field? When we look at the marketer's landing page, there is a content which has a rich text field. And do you see that the component, there's a component here that has some data and some image. There is a CTA button that has some headline and some button text and so on. Now this is a published version of the page so we cannot directly change it. But when I click on create new version, you see that I can go in, open the component and change the stuff here. What I can do is I can also create a new component. And say that, hey, these are only the accepted content types, are CTA, hero or text to image. So I can potentially add a new one here. Yeah, we can take a look at that later. But just so we know that these are the components that the marketer adds into the rich text. So, hopefully that's clear, so let's get back here. Now, what I wanna do in here first is copy the navigation. And as I mentioned before, the navigation expects in the data, it expects all the elements, all the components that are in the rich text field, right? So in case of the marketers landing page, it's gonna be the three components, the hero, the text to the image, and at the bottom there's CTA button. So what I'm gonna do here is I'm gonna use the page elements and I'm looking for content. That's the rich text element. You see here, it's already saying rich text element. So I'll use that. And I'm gonna do linked items. Linked items is a special array. You see, it's an eye content item array that contains all the linked items that are used in the rich text field. So when you look at this, we have the three components. All of these components, all of their data will be available in the linked items property as an array. Now there is a problem here. What's the problem? It's not assignable. Yes, so right now we see that this is eye content item array, right? But in the nav component, we said that we are only looking for component CTA model, component hero model, or component text with image model. So it's telling me, hey, eye content item array is not assignable to the type that you defined, right? So what do we need to do is say here that this is going to be component CTA model or component hero or component text with image model. Oh, and it's of course, an array, right? So we're saying that this array that used to be eye content item, we're gonna reassign it or cast it to a type that contains component CTA model, component hero model or component text with image model and it's an array. Of course, it's not ideal to define it this way because if we wanna add a component or remove one component from the list, we're gonna have to do it on multiple places. If we use the NAV on multiple pages, we'd have to do the same thing on all of them. So what you can do is define a global interface that defines all of these and you would use it on this place, on this page, you would use it in the NAV component and elsewhere. But here for the sake of this example, let's just have it leave it here. So we're retyping the linked items. The array of all the components used in the content field. We're casting that as a component CTA model, component hero model and component text with image model because we know that there can be nothing else. Only these three are allowed in that field. And that's fine, I think for the navigation, let's save it. And let's take a look on the website.
18. Resolving Rich Text Elements into React Components
Now let's resolve the rich text elements into React components using the content React components package. Import the rich text element and provide the page elements content. Then, define the resolvers, specifically the resolve linked item method, to render the components based on their type. We can switch between the CTA, hero, and text with image components using the component CTA model.
So you see right now I'm on a Hawke's Eye rock and there are two components that were rendered as the navigation on the right side. You see, there's a Check Out, The Great Nature and Hawke's Eyes rock. So this is the CTA. This is the text with image. Right, so it seems to that this one is working. So you can quickly verify that this is working for YouTube. It should be. And the last thing you need to do is the rich text resolution. So for that, I'm just gonna create here another diff so that we get a little bit nicer structure. Yeah, still not ideal but it's gonna get better when we resolve the rich text. Now to resolve the rich text elements into React components. We're gonna need one additional package. And the package is called content React components. There is a link to it here. So let me open the GitHub repo. It's already installed in the project so you don't have to install anything. It's already there in your projects. What I wanna show you is that this is an element. This package, as an element that is called the rich text element. And the only thing you need to do is you need to provide the elements to it and define the resolvers. Now, I'm gonna take you through that in a second for our specific use case. But just so you know that you can use this element to resolve linked items, which are our components. We call them the same way, linked items or components is the same thing. But we can also resolve images. If you want to use images with the next image or another library, you can resolve images here. You can resolve also links and also the dumb nodes. So depending on what kind of level of abstraction you're comfortable with, you can either resolve the top level components like linked items, but it can also resolve specific dumb nodes in the response. We're gonna stick to resolving linked items cause that's quite an easy task. And that's what we need, right? We want to get the data and we want to use the right components for the data. So instead of the page elements title value, I'm gonna put here a rich text element. If you're not getting the rich text elements automatically, it should add the import. But if you're not getting it, you're gonna need this line. Import rich text element from at simply 207 org slash content React components. The reason why this is not under Kenty Co is because it's not in a production version yet. So that's why it's outside. But it's gonna get to a production version soon, hopefully. So this is the rich text element. So we need to import from there. And as I showed you, the only thing you need to do is to provide rich text element. Which in our case is page elements and content. No value, no, nothing else is needed. Just do a page dot elements dot content. So this is the rich text element. And the next thing you need to do is provide the resolvers. Now resolvers is an object that contains all the resolvers that you can think of. Now in the resolvers, this is actually going to be object. I'm just going to do two brackets so that it knows what we're doing. And I'm going to put here, rich, resolve, yep. So you see, when I start writing the object properties, I'm going to get four options here. So resolve dumb nodes, resolve image, resolve link or resolve linked item. So that's what I actually want to do. Resolve linked item. And we're going to get the linked item. That's each component, right? So this method is going to be called three times for this specific page. And I'm going to get the linked item in that function so that I can render anything I want here, yeah? I can do return diff hello world. I can definitely do that. Yeah, I'm already resolving that. Let's close the, oh, what's wrong here. Yeah, one missing bracket here. Yeah. So if I want to do it this way, I'm resolving every linked items, so every component that I have in the landing page will be resolved as diff hello world. If I save this and go to the page, you see there are two hello worlds here. Not ideal but do you see that this function is called two times. It's giving me the linked item and I decide to use hello world as the result. And from here, it's only a small step to actually have everything implemented, right? So the only thing we need to do here is do a switch linked item. And we obviously want to switch it based on the type of the linked item, right? We have three types. We have the CTA hero and text with image components. So we wanna distinguish them by type. And of course we could put here component CTA and return the right component. Component CTA model, of course this is not a linked item but the CTA actually expects component CTA model.
19. Working with Rich Text Elements
We have a rich text field in the landing page content that contains components and text. The rich text element allows us to map the data to React components. The page element contains the content, including linked items, which are resolved using the rich text element.
So what we have here in the scribble of the landing page is we're gonna get the page, right? The page that is the landing page as we have it defined in the CMS. And the content field here, let me create a new version of that. So here the content is a rich text field, right? We can, we don't have to add just components here. We can put here text as well. That's why it's called rich text, right? And we have four components here. So we've got all the four components in the code, but what we want is actually to map all these data to those specific React components that we have in the code. That's what we do using the rich text element. So in the page, we have the element content and in the rich text element, that's a special element that's coming from an external library. You see the import here. It's coming from simply 0.007 org content React components. And what it does is you need to provide the rich text elements to it. So that's the page elements content. And this field actually does not contain only the HTML, but it also contains the linked items. See here, those are the linked items. Those are the components in an array. So what this element does is it iterates over those items and lets you resolve them.
20. Resolving Rich Text Elements into React Components
We use the rich text element to map the data from the CMS to specific React components in our code. The rich text element iterates over the linked items and resolves them based on their system type. Each component in the code corresponds to a specific system type, such as CTA, hero, or text with image. The components receive the linked item data as the main model and render accordingly. This allows us to dynamically render the page content with the appropriate React components.
So what we have here in the scribble of the landing page is we're gonna get the page, right? The page that is the landing page as we have it defined in the CMS. And the content field here, let me create a new version of that. So here the content is a rich text field, right? We can, we don't have to add just components here. We can put here text as well. That's why it's called rich text, right? And we have four components here. So we've got all the four components in the code, but what we want is actually to map all these data to those specific React components that we have in the code. That's what we do using the rich text element. So in the page, we have the element content and in the rich text element, that's a special element that's coming from an external library. You see the import here. It's coming from simply 0.007 org content React components. And what it does is you need to provide the rich text elements to it. So that's the page elements content. And this field actually does not contain only the HTML, but it also contains the linked items. See here, those are the linked items. Those are the components in an array. So what this element does is it iterates over those items and lets you resolve them. So in the resolver, you define the link items resolver, which is just a function that lets you convert the data of the component into anything you want. And here, I'm actually switching the linked items system type, because based on the type of the component, I can decide what is the React component in my project. So here, if the system type corresponds to a component CTA, in that case, I'm gonna return a CTA object or a CTA component that I have here, right? This is coming from the component CTA that I have under components. And the same principle is applied to all the other components. In case it's a hero component, I'm gonna return this React component. If it's a text with image component, in the content, I'm gonna return the text with image component that is the React component here. Now, of course I prepared all these components to receive the linked item. So they receive the data as the main model. What you can see here under component CTA, for example, is that this is a functional component that receives the component CTA model. Yeah, so this is the linked item. This is the linked item that I'm getting here. It's the data of the component. And I'm just providing it to the component. And at this point, I don't care what the component does with it. I just expect it to render something. And what this does is it renders the page based on content and uses the right components for where there should be.
21. Resolving Rich Text Elements into React Components
In the landing page, the content field is a rich text field that can contain both components and text. The rich text element from the external library resolves the linked items in the content and maps them to specific React components based on their system type. Each component receives the linked item data as the main model and renders accordingly. The page is rendered with the appropriate React components for each linked item.
Okay. Yeah, no problem. No problem. So what do you need me to explain? The rich text element? Yes, sir. Thank you. Sure, no worries. So what we have here in the scribble of the landing page is we're gonna get the page, right? The page that is the landing page as we have it defined in the CMS. And the content field here, let me create a new version of that. So here the content is a rich text field, right? We can, we don't have to add just components here. We can put here text as well. That's why it's called rich text, right? And we have four components here. So we've got all the four components in the code, but what we want is actually to map all these data to those specific React components that we have in the code. That's what we do using the rich text element. So in the page, we have the element content and in the rich text element, that's a special element that's coming from an external library. You see the import here. It's coming from simply 0.007 org content React components. And what it does is you need to provide the rich text elements to it. So that's the page elements content. And this field actually does not contain only the HTML, but it also contains the linked items. See here, those are the linked items. Those are the components in an array. So what this element does is it iterates over those items and lets you resolve them. So in the resolver, you define the link items resolver, which is just a function that lets you convert the data of the component into anything you want. And here, I'm actually switching the linked items system type, because based on the type of the component, I can decide what is the React component in my project. So here, if the system type corresponds to a component CTA, in that case, I'm gonna return a CTA object or a CTA component that I have here, right? This is coming from the component CTA that I have under components. And the same principle is applied to all the other components. In case it's a hero component, I'm gonna return this React component. If it's a text with image component, in the content, I'm gonna return the text with image component that is the React component here. Now, of course I prepared all these components to receive the linked item. So they receive the data as the main model. What you can see here under component CTA, for example, is that this is a functional component that receives the component CTA model. Yeah, so this is the linked item. This is the linked item that I'm getting here. It's the data of the component. And I'm just providing it to the component. And at this point, I don't care what the component does with it. I just expect it to render something. And what this does is it renders the page based on content and uses the right components for where there should be. Yeah, so here we have the hero image. Here is the text of the image. And here, of course, this doesn't look very nice because I changed it. But there are two CTAs at the bottom, one for the Ukrainian's nature and one for TypeScript Congress. I hope that makes sense.
22. React Workshop Introduction
Yeah, yeah. No worries, no worries at all. We've all been there. I'm no react expert. Yeah. So, don't worry about that. Don't be nervous. It's perfectly fine. There is a GitHub repository that contains the code of this workshop. I will ask the git nation guys if they can with the recording, if they can share also the the implemented version. Yeah, of the workshops. So this is the repository of the starter. And I'm going to also share the repository of the implemented project. Yeah. So you can take a look.
23. Content Model Generation and Build Process
During the build process, the script generates models from the connected projects and compares them with the committed models. If there are any differences, the build fails, ensuring the website's integrity. This approach helps identify unexpected changes and ensures that the content models in the repository are the source of truth. The script provides detailed information about the differences and stops the build, allowing you to react accordingly. You can learn more by referring to the provided blog post.
So when you do a change, code change, a pull request, whatever, and that scope, when you're building the page, the build actually has some conditions before it starts or after it starts, right? We, for example, do... First, we do a build and then we start the unit testing, we start playwright tests for end-to-end testing, and so on. And in scope of that process, we're also checking, we're also generating all the models from the connected projects. And we're checking if all the models are the same with the ones that are already committed in the project. This will always show you if there are any unexpected changes. If someone goes in the project and removes a field, removes an element, removes a content type, or the other way around, if they add a content type, if they add an element, there will always be a change in the generated models. The model generation is quite cheap operation, so you can afford to do it during every build.
So this script, what it actually does is it creates a folder. It generates the models. So this is the same as the script that I was showing you earlier. And when the models are generated, it actually uses a package dir-compare that compares the types in the generator folder with the one that you already use in your project that are committed to your Git repository. And if everything is okay, then it's returning a successful status code. If there are any differences, then it's gonna show you what are the differences, and it's gonna return a process, a process that exits one. So it's a failing code, so the build will eventually fail, but the failing build is still a better option than having the website not working, right? So this is something that actually helps you further because some of the problems that I told you about can be discovered during the build, because the build will just not go through if you're using a missing element or something. But in case that the content model is changed without you knowing, this script will actually figure it out, and it will stop the build and you will exactly, at the time of build, you will exactly know what is going on.
So it shows you something like this. So there are two differences. In the models folder, there are differences in the project structure, and the block with image actually has a different set of fields. So this is just one thing that I wanted to show you that it does not stop there with type scripting all of the content that you have, but it can go further. You can define very strictly that the content models that are in your repository are the source of truth, and if there are any changes between the content model and the generated files in your code base, then it fails the build and lets you figure out the situation and react upon that information. So that's just one last bits of information that I wanted to show you. I'm gonna share the link to the blog post with you so you can take a look. And that's everything from me for today. So we are a bit faster than I expected, which is always good.
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported. The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials. React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.
Are you a React developer trying to get the most benefits from TypeScript? Then this is the workshop for you. In this interactive workshop, we will start at the basics and examine the pros and cons of different ways you can declare React components using TypeScript. After that we will move to more advanced concepts where we will go beyond the strict setting of TypeScript. You will learn when to use types like any, unknown and never. We will explore the use of type predicates, guards and exhaustive checking. You will learn about the built-in mapped types as well as how to create your own new type map utilities. And we will start programming in the TypeScript type system using conditional types and type inferring.
If you're looking to get the most out of TypeScript, this workshop is for you! In this interactive workshop, we will explore the use of advanced types to improve the safety and predictability of your TypeScript code. You will learn when to use types like unknown or never. We will explore the use of type predicates, guards and exhaustive checking to make your TypeScript code more reliable both at compile and run-time. You will learn about the built-in mapped types as well as how to create your own new type map utilities. And we will start programming in the TypeScript type system using conditional types and type inferring. Are you familiar with the basics of TypeScript and want to dive deeper? Then please join me with your laptop in this advanced and interactive workshop to learn all these topics and more. You can find the slides, with links, here: http://theproblemsolver.nl/docs/ts-advanced-workshop.pdf And the repository we will be using is here: https://github.com/mauricedb/ts-advanced
This workshop teaches you the basics of serverless application development with TypeScript. We'll start with a simple Lambda function, set up the project and the infrastructure-as-a-code (AWS CDK), and learn how to organize, test, and debug a more complex serverless application. Table of contents: - How to set up a serverless project with TypeScript and CDK - How to write a testable Lambda function with hexagonal architecture - How to connect a function to a DynamoDB table - How to create a serverless API - How to debug and test a serverless function - How to organize and grow a serverless application Materials referred to in the workshop: https://excalidraw.com/#room=57b84e0df9bdb7ea5675,HYgVepLIpfxrK4EQNclQ9w DynamoDB blog Alex DeBrie: https://www.dynamodbguide.com/ Excellent book for the DynamoDB: https://www.dynamodbbook.com/ https://slobodan.me/workshops/nodecongress/prerequisites.html
This workshop will walk you through writing a module in TypeScript that can be consumed users of Deno, Node and the browsers. I will explain how to set up formatting, linting and testing in Deno, and then how to publish your module to deno.land/x and npm. We’ll start out with a quick introduction to what Deno is.
In this workshop, I’ll first show you how to create a new project in a headless CMS, fill it with data, and use the content in your project. Then, we’ll spend the rest of time in code, we will: - Generate strongly typed models and structure for the fetched content. - Use the content in components - Resolve content from rich text fields into React components - Touch on deployment pipelines and possibilities for discovering content-related issues before hitting production You will learn: - How to work with content from headless CMS - How content model can be leveraged to generate TS types and what benefits it brings to your project - How not to use string literals for content in code anymore - How to do rich text resolution into React components - How to minimize or avoid content-related issues before hitting production
TypeScript and React are inseparable. What's the secret to their successful union? Quite a lot of surprisingly strange code. Learn why useRef always feels weird, how to wrangle generics in custom hooks, and how union types can transform your components.
The more you keep working on an application, the more complicated its routing becomes, and the easier it is to make a mistake. ""Was the route named users or was it user?"", ""Did it have an id param or was it userId?"". If only TypeScript could tell you what are the possible names and params. If only you didn't have to write a single route anymore and let a plugin do it for you. In this talk we will go through what it took to bring automatically typed routes for Vue Router.
Why are we devs so obsessed with decoupling things that are coupled nature? tRPC is a library that replaces the need for GraphQL or REST for internal APIs. When using it, you simply write backend functions whose input and output shapes are instantly inferred in your frontend without any code generation; making writing API schemas a thing of the past. It's lightweight, not tied to React, HTTP-cacheable, and can be incrementally adopted. In this talk, I'll give a glimpse of the DX you can get from tRPC and how (and why) to get started.
If you're using a headless CMS for storing content, you also work with URL slugs, the last parts of any URL. The problem is, content editors are able to freely change the slugs which can cause 404 errors, lost page ranks, broken links, and in the end confused visitors on your site. In this talk, I will present a solution for keeping a history of URL slugs in the CMS and explain how to implement a proper redirect mechanism (using TypeScript!) for dynamically generated pages on a Next.js website. Add to the talk notes: https://github.com/ondrabus/kontent-boilerplate-next-js-ts-congress-2022
In this talk, we will be going over a number of common useful and best-practice-proven TypeScript patterns to use in React 18. This includes how to correctly type component properties, children and return types, using React's built-in types, typing contexts, and the usual enum rant (but constructively).
We all love writing types in TypeScript, but we often find ourselves having to write types in another language as well: SQL. This talk will present the choose-your-own-adventure story that you face when combining TypeScript and SQL and will walk you through the tradeoffs between the various options. Combined poorly, TypeScript and SQL can be duplicative and a source of headaches, but done well they can complement one another by addressing each other's weaknesses.