Building and Deploying a Modern Blog Platform with Next.js, Strapi, and Kubernetes – Part 2: Adding the Frontend



Copy-Pasting sounds boring and outdated? Try our ready template

This is Part 2 of a 3-part tutorial series designed to help you build a modern, scalable, and secure blog platform using Strapi, Next.js, and Kubernetes.
In this part, we’ll add a frontend to our blog using Next.js, connect it to our Strapi backend, and expose it to users through Ingress.
If you haven’t completed Part 1, make sure you have a working Strapi deployment with PostgreSQL and a configured Ingress before continuing.
Building a Modern Blog with Strapi & Kubernetes: Your First CMS in the Cloud
A Headless CMS like Strapi separates content management from the frontend, delivering content via APIs (REST or GraphQL). This works perfectly with frameworks like Next.js, where you control how content is displayed.
By using a headless CMS in Kubernetes, you get a flexible, high-performance architecture built for modern web apps.
When building a frontend for a blog, you want more than just a beautiful UI—you want speed, discoverability, and reliability.
Next.js is ideal for this because it offers:
For a blog, where search engine visibility and snappy content delivery matter, Next.js is the gold standard.
Before we dive into deployment, let's understand how our Next.js frontend will interact with Strapi:
This architecture gives you the best of both worlds: the editorial experience of a traditional CMS with the performance and developer experience of a modern frontend framework.
First, let's understand what goes into a typical Next.js blog application that connects to Strapi:
Key Components:
The Next.js frontend connects to Strapi using GraphQL with proper authentication. The key integration points are:
Environment Variables Required:
NEXT_PUBLIC_STRAPI_URL - Your Strapi backend URLNEXT_PUBLIC_STRAPI_TOKEN - API token for authentication with Strapi GraphQL endpointKey Features:
💡 Complete Source Code Available: You can find the complete, working Next.js frontend code in our GitHub repository: GitHub Repository
The repository includes:
Important to note is the use of environment variable NEXT_PUBLIC_STRAPI_URL. This contains the URL of the Strapi deployment and the NEXT_PUBLIC_STRAPI_TOKEN for authentication.
Your Next.js application needs to be containerized for Kubernetes deployment. The Docker setup uses a multi-stage build process for optimal production deployment:
Why Use Next.js Standalone Server?
We're using Next.js standalone output mode, which creates a self-contained deployment that includes all necessary dependencies. This approach provides several advantages:
Standalone Server Configuration
The next.config.js is configured with output: 'standalone', which creates a minimal production server. Environment variables are embedded at build time for security and performance.
The repository contains a docker file, use the docker file to create and run or publish the image.
Build and push your image:
# Build the image with environment variables
docker build \
--build-arg NEXT_PUBLIC_STRAPI_URL=http://strapi-service:1337 \
--build-arg NEXT_PUBLIC_STRAPI_TOKEN=your-api-token \
-t yourdockerhub/nextjs-blog:latest .
# Test the image locally (optional)
docker run -p 3000:3000 yourdockerhub/nextjs-blog:latest
# Push to registry
docker push yourdockerhub/nextjs-blog:latest
Now let's perform the Kubernetes deployment of the containerized Next.js application. Here's an enhanced deployment with production-ready configurations:
apiVersion: apps/v1
kind: Deployment
metadata:
name: blog-frontend
spec:
replicas: 1
selector:
matchLabels:
app: blog-frontend
template:
metadata:
labels:
app: blog-frontend
spec:
containers:
- name: nextjs
image: yourdockerhub/nextjs-blog:latest
ports:
- containerPort: 3000
Understanding the Standalone Server Deployment:
We're using Next.js standalone server mode, where the environment variables (NEXT_PUBLIC_STRAPI_URL and NEXT_PUBLIC_STRAPI_TOKEN) are embedded into the standalone output during the Docker build process. This means:
--build-argThis deployment connects the frontend to the Strapi backend using strapi-service (strapi-service:1337). Remember we created this service in the Part 1. In your deployment of Strapi you also need to install GraphQL plugin to be able to query Strapi through GraphQL.
Building a Modern Blog with Strapi & Kubernetes: Your First CMS in the Cloud
Just like with Strapi, the Next.js frontend must also be exposed via a Kubernetes Service. This allows other resources—such as an Ingress—to reach the application reliably.
In the service definition below, the selector field must match the matchLabels used in the frontend deployment, ensuring traffic is routed to the correct pods. Similarly, the targetPort must match the containerPort defined inside the deployment. These values ensure that network communication from Kubernetes Services correctly maps onto your running containers.
apiVersion: v1
kind: Service
metadata:
name: frontend-service
labels:
app: blog-frontend
spec:
type: ClusterIP
selector:
app: blog-frontend
ports:
- name: http
protocol: TCP
port: 3000
targetPort: 3000
A Service in Kubernetes provides several critical functions:
blog-frontend)In the service definition above:
selector: Must match the matchLabels used in the deploymentport: The port that other services will use to connecttargetPort: The actual port your application listens on inside the containerApply the service:
kubectl apply -f blog-frontend-service.yaml
Test the service internally:
# Get a shell in any pod in the cluster
kubectl run -it --rm debug --image=busybox --restart=Never -- sh
# From inside the pod, test the service
wget -qO- http://frontend-service:3000
Now as in previous article (Part 1) we used port forwarding to view the application running in browser, you can do the same to access your frontend in your browser.
Building a Modern Blog with Strapi & Kubernetes: Your First CMS in the Cloud
kubectl port-forward service/frontend-service 8080:3000
In real setups this is not what you would like to do. We create software so that others can use it and not to run on local. To make the application available without port-forwarding, we need to make it accessible from outside the cluster. One way of doing so is with NodePort service. Another way is with Ingress.
Ingress is Kubernetes' way of managing external access to services in your cluster, typically HTTP and HTTPS traffic. Think of it as a smart reverse proxy that sits at the edge of your cluster and routes incoming requests to the right services based on rules you define. Unlike NodePort services that expose individual services on random high ports, Ingress gives you clean URLs and can handle multiple services through a single entry point. It also provides advanced features like SSL termination, path-based routing, and load balancing - making it the preferred way to expose web applications to the internet.
Before we start configuring Ingress we need to install Ingress Controller in our cluster. Following is the command to install Ingress Controller in Docker Desktop.
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/cloud/deploy.yaml
Wait for it to be ready
kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=300s
For a better understanding about Namespaces, read our blog, Namespaces Are Not a Security Boundary — Stop Pretending They Are
So far, we've been using port-forwarding to access both Strapi and our Next.js frontend. While this works for development, we need to configure Ingress to make both applications accessible from the internet. We'll set up routing rules so that Strapi (for the admin interface) and the Next.js frontend (for the public blog) are both available through clean URLs.
Kubernetes Ingress allows you to route HTTP/HTTPS traffic based on the request path:
In our case:
/admin → Routes to Strapi for content management/ → Routes to Next.js frontend for the public blogapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- http:
paths:
# Strapi admin interface
- path: /admin
pathType: Prefix
backend:
service:
name: strapi-service
port:
number: 1337
# Next.js frontend (catch-all)
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 3000
The order of paths in the Ingress matters:
/admin is more specific than // will match any path that doesn't match the aboveThis ensures that:
yourdomain.com/admin → Goes to Strapi adminyourdomain.com/, yourdomain.com/blog/my-post → Goes to Next.jsApply the updated Ingress:
kubectl apply -f blog-ingress.yaml
First, make sure your Ingress controller is running and has an external IP:
kubectl get ingress blog-ingress kubectl get services -n ingress-nginx
Once your Ingress has an external IP, test different paths:
# Get the Ingress IP
export INGRESS_IP=$(kubectl get ingress blog-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# Test the frontend
curl http://$INGRESS_IP/
# Test Strapi admin (should redirect to login)
curl http://$INGRESS_IP/admin
Meanwhile, /admin will still take you to the Strapi CMS interface for content management.
The following diagram cleanly describes what we have done so far.
Congratulations! You've successfully completed a Kubernetes deployment of a full-stack blog platform with a Next.js frontend and Strapi CMS backend. In this part, we accomplished several key milestones:
/admin goes to Strapi, and / displays your beautiful blogYour blog is now functional and accessible to you without port-forwarding! However, there's still one critical limitation: your content disappears every time the Strapi pod restarts because we're still using Strapi's default in-memory database inside the container.
Coming up in Part 3: We'll make your blog truly production-ready by configuring a persistent PostgreSQL database to ensure your content survives pod restarts, and we'll set up AWS S3 for reliable media storage so your images and files are safely stored outside the cluster. No more losing your hard work when Kubernetes reschedules pods!