[{"content":"I have always started my new web projects off with vanilla React, then added routing (react router), state management (zustand), etc., once I had the need for them. As the project would grow over time, more and more packages would need to be added. Eventually, you basically end up with your own custom framework. This is why the React team has started to recommend building new apps and websites with an existing React framework. Some currently supported production ready frameworks include Next.js, Remix and Gatsby. However, at the time of this writing, only Next.js supports all the latest React features, such as React Server Components (RSCs). Therefore, given the React teams recommendation to start new projects off with a framework, my own experience with building \u0026ldquo;custom\u0026rdquo; React frameworks and the ability to utilize cutting edge React features such as RSCs, I have started using Next.js for my new web apps. Here are a few challenges that I ran into, from the perspective of a React developer.\nEverything is a Server Component By default, when creating a new component with Next.js, that component will be a server component. This means that the code in that server component will be rendered on the server. Therefore, any \u0026ldquo;reactive\u0026rdquo; code (such as useState, useEffect, etc.) cannot be used in the server component. If you want to use these hooks, you will need to create a new component and annotate it with the \u0026quot;use client\u0026quot; directive. Note: the sever component has no such annotation. All components are simply server components by default. This design encourages you to do the bulk of the rendering on the server, and thinking very carefully about what code needs to respond to user interaction, and which code is simply \u0026ldquo;static.\u0026rdquo; For example, here is the code for the Header component from lalisolari.com:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { FaInstagram } from \u0026#34;@react-icons/all-files/fa/FaInstagram\u0026#34;; import LanguageDropdown from \u0026#34;./languageDropdown\u0026#34;; import Logo from \u0026#34;./logo\u0026#34;; export default function Header() { return ( \u0026lt;header className=\u0026#34;flex flex-col bg-transparent shadow-none py-2\u0026#34;\u0026gt; \u0026lt;div className=\u0026#34;flex justify-between items-center\u0026#34;\u0026gt; \u0026lt;Logo /\u0026gt; \u0026lt;div className=\u0026#34;flex flex-row pr-2 md:pr-4\u0026#34;\u0026gt; \u0026lt;LanguageDropdown /\u0026gt; \u0026lt;a href=\u0026#34;https://www.instagram.com/lalisolariart/\u0026#34; target=\u0026#34;_blank\u0026#34; rel=\u0026#34;noopener noreferrer\u0026#34; \u0026gt; \u0026lt;button aria-label=\u0026#34;instagram link button\u0026#34; className=\u0026#34;p-2 rounded-full bg-white text-gray-500 hover:bg-gray-100\u0026#34; \u0026gt; \u0026lt;FaInstagram size={23} /\u0026gt; \u0026lt;/button\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/header\u0026gt; ); } We can see that this is a server component that renders a logo, some static icons and buttons, and language dropdown component. That LanguageDropdown component makes use of React useState and useRef, therefore it needs to be a client component. Here is how the start of that code looks:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026#34;use client\u0026#34;; import { useState, useRef, useEffect, useTransition } from \u0026#34;react\u0026#34;; import { MdTranslate } from \u0026#34;@react-icons/all-files/md/MdTranslate\u0026#34;; import { usePathname, useRouter } from \u0026#34;../navigation\u0026#34;; import clsx from \u0026#34;clsx\u0026#34;; export default function LanguageDropdown() { const router = useRouter(); const [isPending, startTransition] = useTransition(); const pathname = usePathname(); const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef\u0026lt;HTMLDivElement\u0026gt;(null); .... Splitting the code in this manner allows for doing the maximum amount of server side rendering, therefore reducing the JavaScript bundle size sent to the client. See this link from the Next.js docs for a full list of the benefits of Server Rendering. Normally, React devs are accustomed to splitting up web pages into many different components (as opposed to one page one component). Next.js and server components now also introduce the idea of splitting the components up further into the sections that can be rendered on the server and those which respond to user action (on the client).\n\u0026lt;Image/\u0026gt; vs \u0026lt;img/\u0026gt; Another concept that I initially struggled with when switching to Next.js was the \u0026lt;Image /\u0026gt; component. In my vanilla React apps, I was used to doing the following:\n1 2 3 4 5 \u0026lt;img height=\u0026#34;auto\u0026#34; width=\u0026#34;100%\u0026#34; ... /\u0026gt; With a wrapping grid component or similar, which determined the size of the images. However, this is not possible with the Next.js Image component, as the width and height are required values which must be integers (in pixels). This is done to prevent layout shift during loading, which is a common cause of a poor user experience. However, this behavior of requiring the width and height values can make the Image component quite unnatural to use at first. Even still, it is important to use Image and not img, as the Next.js variant provides additional benefits, such as being responsive by default, a priority prop, ability to set a placeholder image during loading, as well as other image optimizations.\nHosting Your Next.js App One of my favorite ways to host React apps has been through Cloudflare Pages. I enjoy how simple this method is with linking my GitHub account to Cloudflare for automatic deployments as well as using Cloudflare for purchasing and setting up custom domain names. Given this, I decided to host my first Next.js app through Cloudflare pages. I began following the Cloudflare docs and eventually got the site deployed and \u0026ldquo;working\u0026rdquo;, after adding export const runtime = \u0026quot;edge\u0026quot;; to the top of every file. While I was able to successfully deploy the website, Next.js currently only supports the \u0026ldquo;Edge\u0026rdquo; runtime. Next.js has two server runtimes available: Node.js (default) and Edge. The Node.js runtime has access to all Native Node.js APIs, while the Edge runtime contains a more limited set of APIs. This was a problem, as I was unable to use some packages, such as plaiceholder. It seemed like there were also other issues, such as with the Next.js \u0026lt;Image/\u0026gt; component optimizations and caching. Therefore, I switched to Vercel to host my website. Vercel worked great, and I was easily able to get everything up and running utilizing the latest Next.js features and optimizations. However, this soft \u0026ldquo;vendor lock\u0026rdquo; currently seems like one of the biggest downsides to using Next.js. To get the best experience, using Vercel currently feels required, and I image self-hosting a Next.js app would be even more challenging/limiting. That said, I expect better support for Next.js app hosting in the future, as well as more framework competition as products like Remix begin supporting all the latest React features.\nNext.js: the Future of React Overall, I have been very impressed with Next.js. While it is quite opinionated about certain things (Image component, App router, etc.), the benefits are clear. Specifically, the App router is amazing, and I love using the folder and file naming conventions for pages. Given that the future of React is frameworks, I think that Next.js is well positioned to be the leading player.\n","date":"2024-11-20T13:00:00-05:00","permalink":"https://zacharymjohnson.blog/next.js-for-react-devs/","title":"Next.js for React Devs"},{"content":"Recently, upon migrating Lali Solari\u0026rsquo;s personal website from vanilla React to Next.js (see this Contentful blog post for a great guide on getting started with Next.js App Router and Contentful), I decided to try out the Contentful GrapQL API. In the past, I have always used the Contentful Javascript SDK, which is a great tool for interacting with the Contentful REST API. However, I wanted to use the native web fetch API to take advantage of specific Next.js features, such as caching, so I decided to give the GraphQL API a try. Thanks to the GraphiQL browser tool and the ability to only get back the data that I need, I found the Contentful GraphQL API experience even better than working with the Contentful Javascript SDK.\nGraphiQL as TDD? GraphiQL is an incredible browser based development tool. It allows you to write queries and mutations, with auto-completion, in the browser before even writing a single line of code. Contentful\u0026rsquo;s GraphQL API supplies the GraphiQL tool environment, which can be reached at https://graphql.contentful.com/content/v1/spaces/SPACE_ID/explore?access_token=YOUR_ACCESS_TOKEN. Using the in-browser text editor, I was able to quickly write queries to get the data that I needed, and then test them in the browser. While this is not a replacement for tests in the codebase, it is a great way to see what the return data will look like, before writing any code. But furthermore, this is not wasted effort, as the queries that you write in GraphiQL can be copied and pasted directly into your codebase.\nHere is what one of my GraphiQL queries looked like: Copying this directly into my codebase, I get the following.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const MANIFESTO_PAGE_QUERY = ` query ($locale: String!) { manifiestoCollection(locale: $locale) { items { title description mediaCollection { items { title url } } } } } `; Exactly the Data Needed My other favorite thing about using the Contentful GraphQL API was the ability to get only the data I needed, with minimal boilerplate code. For reference, here is what the call to get the Manifesto page data was in the original React project, using the Contentful SDK:\n1 2 3 4 5 const client = getClient(); const res = await client.getEntries\u0026lt;Manifesto\u0026gt;({ content_type: \u0026#34;manifiesto\u0026#34;, locale: languageMode, }); where Manifesto was\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 export interface Manifesto { title: string; description: string; media: Photo[]; } export interface Photo { fields: { description: string; file: { contentType: string; details: { image: { width: number; height: number; }; size: number; }; fileName: string; url: string; }; title: string; }; sys: { id: string; }; } As you can see, these are very verbose types that I needed to define myself based on the response from the REST API call via client.getEntries\u0026lt;Manifesto\u0026gt;. While I still need to define the response type myself when using the GraphQL API, it is much simpler given that the Photo array is now just a mediaCollection on which I can simply grab the title and url (both of which are strings). This not only simplifies the return type, but also reduces the amount of response code returned via the API call.\nPotential GraphQL Drawbacks The only complaint that I had with using the Contentful GraphQL API versus the REST API is needing to manually create the fetch call. Here is what that ended up looking like for me:\n1 2 3 4 5 6 7 8 9 10 11 12 const res = await fetch( `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}/environments/${process.env.CONTENTFUL_ENVIRONMENT_ID}`, { method: \u0026#34;POST\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, Authorization: `Bearer ${process.env.CONTENTFUL_CONTENT_DELIVERY_API_ACCESS_TOKEN}`, }, body: JSON.stringify({ query, variables }), next: { tags: [\u0026#34;portfolioContent\u0026#34;] }, } ); However, in my case this was required in order to take advantage of the Next.js enhanced fetch API (see the next tag above that facilitates caching). Moreover, this logic only needs to be written once and can then be encapsulated in a function to be used with multiple queries.\nDon\u0026rsquo;t Default to REST Because I was familiar with the Contentful Javascript SDK and that way of querying data, I had simply defaulted to this in my new project. Even though I have used GraphQL before, I was stuck in a RESTful, traditional API mindset. But given the ability to get exactly the data that is need and the excellent developer tools of GraphiQL and associated type completion, I am very glad I tried out using GraphQL again. I hope you also consider using GraphQL in the future!\n","date":"2024-10-03T13:00:00-04:00","permalink":"https://zacharymjohnson.blog/graphql-a-breath-of-fresh-air/","title":"GraphQL: a Breath of Fresh Air"},{"content":"I recently found myself needing to lift some parts of my application state into the URL. I usually default to use some state management solution in my web projects, and I am a huge fan of Zustand. However, there comes a time when using this type of state management alone may not provide all the desired functionality.\nWhy Lift State Normally, when using something like Redux, the global store lives in memory within the JavaScript runtime of your application. This generally would work great, but what if a user wanted to share part of their application state with another user running the application on another computer? Or what if the same user simply wanted to share some application state between different tabs in the same browser? If all your state is stored in runtime memory, this isn\u0026rsquo;t possible.\nThe solution: put the part of the state that you want to share between instances into the URL. For example, on the PhotoVoice Japan website, I wanted users to easily be able to share voices. Initially, a user would have to go to the \u0026ldquo;search\u0026rdquo; tab, explain what search params to use and then also share the title of the voice that they wanted the other user to view. But after lifting the voice ID (in my case I am using Contentful\u0026rsquo;s internal Entry ID, but this can be any unique identifier) into the URL, they need only share a single URL to link directly to a specific voice. For example: https://photovoicejapan.com/display/23z0n4kUPKvV0KYDbwTtAz.\nThis change not only added a new feature in being able to share specific voices, but also simplified the existing Zustand store, by removing the concept of a \u0026ldquo;currentVoice\u0026rdquo; and simply having that \u0026ldquo;currentVoice\u0026rdquo; entry ID live in the URL. Furthermore, I find having this part of the state in the URL can aid in debugging, since \u0026ldquo;reproducing\u0026rdquo; state is much easier by simply sharing a URL, instead of needing to click through multiple UI windows/modals.\nTime to Put Everything in the URL? Given the clear benefits of lifting state to the URL, why not just move everything there? For some things, it probably just isn\u0026rsquo;t important enough to move to the URL or serves no real purpose. For example, the user\u0026rsquo;s light/dark mode preference. This can just live in memory and default to the user\u0026rsquo;s browser preference. If it\u0026rsquo;s very important that this preference is saved, it can be linked somehow to a concept of a user and their \u0026ldquo;profile.\u0026rdquo; Additionally, some pieces of state do not make sense to share. For instance, maybe the user is in the middle of entering data or making selections in a multi-step modal. It is likely that such a workflow would be all or nothing, and it is therefore unnecessary to store the intermediate parts of state in the URL.\nEnjoying the Benefits Multilocation State Lifting state to the URL is helpful when needing to easily share state between users or instances and to aid in state reproduction for debugging and troubleshooting purposes. Utilizing both runtime memory and the URL to manage state is important as applications become more complex. I hope you find that lifting state to the URL can also help improve the functionality of your applications!\n","date":"2024-06-27T18:45:02-04:00","permalink":"https://zacharymjohnson.blog/benefits-of-lifting-state-to-the-url/","title":"Benefits of Lifting State to the URL"},{"content":"Recently, I completed a large data migration on the PhotoVoice Japan website. The purpose of this migration was to clean up existing content models and utilize Contentful’s locales feature to allow for cleaner and more efficient multi-language support. Thanks to Contentful’s use of multiple Environments within a project Space, this migration was quite straightforward.\nCloning your Existing Environment Fortunately, Contentful offers a very generous free \u0026ldquo;Intro\u0026rdquo; space license. Under the Intro license we are allowed three environments.\nHaving access to these multiple environments is what allows us to make a seamless data migration. In your current space you should already have one environment, something like \u0026ldquo;master\u0026rdquo;. To add a new environment, go to \u0026ldquo;Settings\u0026rdquo; in the top right corner and then select \u0026ldquo;Environments\u0026rdquo; from the dropdown. Next click \u0026ldquo;Add environment\u0026rdquo;. This should open a popup which asks you to name the new environment and also asks where you want to copy the new environments data from:\nCloning from the current environment gives us an exact copy of our existing data that we are free to modify without affecting the original environment. This means you will have new copies of both your entries and content types in the new environment. After adding the new environment, you can access it by clicking on your space name in the top left corner and selecting it:\nPerform the Data Migration Now that we have a new \u0026ldquo;v2\u0026rdquo; environment, we can make whatever changes we want to the content models. In my case, this involved cleaning up some fields on various content models and adding a new locale. Contentful\u0026rsquo;s free plan also allows you to utilize up to two locales. This feature can be accessed via \u0026ldquo;Settings\u0026rdquo; then selecting \u0026ldquo;Locales\u0026rdquo; from the dropdown menu. After adding the new locale and fixing the affected entries, we are ready to point our code to the new data.\nPointing Production to the New Environment In order to get your code working with the new environment, we will need to update a few secrets as well as change some API token settings in the Contentful dashboard. First, go to \u0026ldquo;Settings\u0026rdquo; then \u0026ldquo;API keys\u0026rdquo; in the Contentful dashboard. Here, you may either create a new API key or provide your current development access token with access to the new environment. For my use case, I opted for the latter. In the specific access token settings, you will simply want to scroll down to the bottom and make sure both environments are enabled:\nOn the code side of things, you will need to update your \u0026ldquo;environment\u0026rdquo; value from \u0026ldquo;master\u0026rdquo; to \u0026ldquo;v2\u0026rdquo;. Assuming this value is not hard-coded, you will also want to make sure you update the environment string in your deployment pipelines. Once your API key has been updated with the new permissions and your code has been changed to point to the new environment, you should start seeing your site utilizing your \u0026ldquo;v2\u0026rdquo; entries and content models.\nTake Advantage of Contentful Environments Contentful environments are a powerful tool to update existing entries and content models without having to worry about affecting production data. Additionally, they allow you to keep around older versions of the data for future reference or revisions. Given that Contentful\u0026rsquo;s free plan also allows usage of up to three environments, there is no reason not to take advantage of this feature. Hopefully utilizing multiple Contentful environments will allow you to update the content on your site with confidence!\n","date":"2024-05-11T18:45:02-04:00","permalink":"https://zacharymjohnson.blog/streamlining-data-migrations-in-contentful/","title":"Streamlining Data Migrations in Contentful"}]