Creating a Developer Portfolio with Next.js and Cosmic
As a developer, one of the most valuable things you can do is create your presence on the internet. Your portfolio is a great place to showcase your proudest projects to the community or potential employers.
Today, we’re going to build a beautiful developer portfolio, giving you a place to store your blog posts and projects. After completing this tutorial, you will understand how building with modern developer tools comes with great benefits. Faster page loads, optimized SEO, and an intuitive developer experience will ultimately enable you to provide better products for your colleagues and clients.
Tools we’ll be using
To build our developer portfolio, we’re going to use the following technologies:
- Next.js - A React framework that makes it easy to spin up a full-stack application. For this tutorial, we will be leveraging the Next.js 13 App Router.
- Cosmic - A headless CMS enables the independence of the data (content) layer and gives us the ability to quickly manage template content. In this case, our blog and project posts.
- Tailwind CSS - A performant utility-first CSS framework that can be composed directly in your markup.
While the main objective of this portfolio is to be functional and showcase who you are as a developer, I wanted to ensure that we prioritized user experience and overall website performance.
- Organized content management - With Cosmic, we can store all of our content in one place. Once we write the code for our user interface, we will not have to worry about storing content within our project. Cosmic handles all of this.
- Next.js Image optimization - With the power of Next Image, we will ensure that our images won’t be slowing us down one bit. Storing local images of ourselves (if you prefer to include those) as well as the remote images we will query from our Cosmic bucket, we will make sure to utilize things like lazy loading, placeholder blur, and built-in image optimization from Next.js.
- SEO and Accessibility best practices - As a web developer, it is crucial that you cater to good semantics to ensure that your site is accessible by anyone and discoverable by everyone. Good SEO will get your name out there and ensure the best chance for your articles to rank in searches.
Starting from a blank Next.js app
To get started with this template, let’s create a new Next.js application.
Then install the dependencies.
Let’s fire up our application! After running the command below, you can open up http://localhost:3000 in your browser.
Getting started with Cosmic
First things first, let’s create a free Cosmic account. Once created, we can go ahead and create a new project. Select “Empty project”, then you can name your project. Since this is our primary bucket where we will be building and deploying our project, I’m going to name the bucket environment “Production”. Go ahead and select “Save Bucket”.
Next, we can start adding objects to our Cosmic Bucket.
The Content Model
The Content Model is a blueprint for our object. It consists of data that can be as simple as one single text value or as complex as storing several data values. These could be strings, numbers, booleans, etc. The whole point of configuring this content model is so that every time we create a new blog post, all of the Metafields we have created in our blueprint will be there for us to fill out.
To add our custom Metafields to the content model, we can drag a Metafield from the list on the right and drop them into our Metafields container.
Let’s create our Categories object. The only piece of data we will need for our categories object is going to be the title, so we do not have to add any Metafields.
This is what our content model will look like for the posts object. The Metafields will be:
- Category - Which we will link to our Post Categories object. Metafield type: Relationship: Single Object.
- Cover Image - An image we can display at the top of our post. Metafield type: Image.
- Excerpt - A short sentence summarizing our post. Metafield type: Text Input.
- Content - The text content that will go inside of our post. Metafield type: Markdown.
Note that by default, when we create a new object it will have a content and slug field. We will be using the slug (which Cosmic generates for us) in our code to properly route our posts.
Before we dive into the code, go ahead and create a post with sample data so that we can test it later on.
We will also be making a “Works” object to showcase our best projects. Copy the blueprint for the “Posts” object, though add in two additional Metafields. These will be:
- Repo URL - A link to the project GitHub repository. Metafield type: “Text Input”.
- Live URL - A link to the live website for your project. Metafield type: “Text Input”.
Setting your environment variables
We will need to create three environment variables inside of a .env file in the root of our project. The Bucket Slug and Read Key can be found in your dashboard in Settings > API Access. The preview key is something that you can define yourself, so go ahead and create your own preview secret key so that you can use it later on.
Getting our Posts
Now that we have our environment variables set, we are ready to access the Cosmic API and retrieve our data.
While getting our posts, we can also create a few parameters here. In our example, getting our "Posts" and "Works" will share the same function, though we will pass in an argument when we call the function declaring which object to get. We can do this for our categories as well by passing the title of our Cosmic object as an argument.
To get a better understanding of how we are getting our data from Cosmic, let’s break down all of the methods we are using in the code above:
- - Declares which Object Type we want to fetch by setting the
- - Filters the data we want to fetch from the Object Type. The more we optimize this method, the more we can reduce the payload of data we receive from Cosmic, which is important for scaling.
- - Limits the number of Objects we are initially fetching.
- - Sorts the Objects we are fetching, and in this case, by the date the Object was created at.
- - Declares the status of the Objects we want to fetch. Published Objects return with a status of 'published' while unpublished Objects return 'draft' . When draft mode is enabled, we can return all Objects by passing an argument of 'any'.
Parsing the markdown
Since we will be writing our content in Markdown, we will need a way to serialize the markdown into HTML. To do this, we will install react-markdown. React Markdown is a package that can be given a string of markdown to React elements from. I explain in-depth how this package parses markdown and renders HTML in this article if you're interested in learning.
Now that we’ve installed react-markdown, let's create a component that will parse the markdown coming from Cosmic and render it as HTML. I'm going to name this component .
Here's the breakdown of this component:
- We import the actual dependency for rendering markdown along with some CSS styles and the Next Image component (more about that in a moment)
- We create an object of for which we add custom capability, taking in the plain markdown from Cosmic and rendering HTML tags and React components with extra attributes.
- We Create the actual React component, , and then pass the markdown styles and object into the component's options, then pass the actual markdown string as a of the component.
Creating a list of posts
Now that we’ve done the basic setup of our Cosmic bucket, created a .env file with the required environment variables, created the functionality to get the data from Cosmic, and parsed our markdown and rendered HTML from it, we can create a list of posts so that users can read them.
For our post list, we will display the title and the excerpt from the post. This is what it will look like:
We can create a component so that we can reuse our post list with ease on several parts of our site. When we render this component on one of our pages, we will pass the data of all the posts we receive from Cosmic to the parameter.
Creating a filter for our list of posts
Let's create the functionality to filter our by a specific category:
Rendering the list of posts on the page
Now, let’s take that post list and render it on our page. If you haven’t yet, create a folder inside of your folder called . Then, let’s create a file called where our will live.
With the Next.js App Router, we will call the functions we created earlier to get those posts from Cosmic directly in our file. The awesome part about this is that when it comes to build time, these posts will be built statically and deployed to an edge CDN, making the pages available to users around the world within milliseconds.
We are also calling our function that gets the post categories so that we can then display them on the page and create the functionality to filter through the posts.
Creating an individual post page
In the folder (), let’s create a file. This is where we can write the code for our individual posts.
The content for our post page will be composed of three components:
- - Containing our PostTitle, post metadata (date and category), and cover image.
- - The title of the post.
- - The component handling the markdown to HTML conversion from earlier.
- - A stylesheet specifically for our markdown so that it is readable.
The actual page:
Now that we’ve implemented the code into our page, we can click on any of the posts from the Post List and view the content of our post.
Creating the Work list and Work page
Now that we have the structure for our post page and post index page, we can repeat this for our work page. We can create a folder in our folder, followed by a and a second inside of a folder.
Copy the code from both the and in , and simply change the instances of “post(s)” to “work(s)”.
Using Draft Mode
With Next.js Draft Mode and Cosmic, we can view drafts of our unpublished posts privately. We do this by setting a cookie in your browser and setting a custom state for your application. In Cosmic, create a post, and once you have filled out the fileds, select “Save Draft” rather than “Publish”.
Before we can preview our post, let’s set our app up so that we can utilize this functionality.
- Grab the COSMIC_PREVIEW_SECRET you created earlier. Then click on the settings icon on your object in Cosmic.
- Scroll down to the “Preview Link” field. Replace the YOUR_SECRET_PREVIEW with your own COSMIC_PREVIEW_SECRET, and make sure that the OBJECT_TYPE_PATH matches the slug of the Object Type you are configuring (either 'posts' or 'works'). What we are doing here is telling our application to go to this route if the post (or work) has a status of “draft”.
Note that our link is set to local host and draft mode will only work when we are running our local development server. Once your app is deployed, you can replace “http://localhost:3000” with your domain name.
- Let’s go back to our cosmic.js file and create a function that gets the preview post from Cosmic.
- Now let’s create two API routes in our project - one for the preview itself and the other for exiting the preview. Fortunately, Next.js handles API routes out of the box.
- Now we can go back to our post in Cosmic, and select the “preview” button and our application will open up the preview of our post.
- Before we’re done with our preview mode, we need to create a component that alerts us if we are in preview mode, with a link to exit the preview mode. This link takes us to that “exit-preview.js” API route we created above. This is a nice to have and indicates to us that we are viewing unpublished content that is not live on our site.
- Now that we have our banner made, all we need to do is import it into our main layout component in our app directory. By default, our Cosmic object comes with a “status” key value pair. If our post is not published, it has a status of “draft”. the hook in will return either or and conditionally render the banner we just created!
Deploying to Vercel
To deploy your project to Vercel, click here. This link will automatically clone the template into a new repository and build and deploy your new application. All you have to do is provide the environment variables from earlier.
Now you have a fully functional developer portfolio that you can use to showcase your projects and share your blog posts with the tech community. I hope you enjoyed this tutorial, and if you have any feedback or questions, feel free to join us over at the Cosmic Slack Channel.