Building and Deploying a Modern Blog Platform with Next.js, Strapi, and Kubernetes – Part 2: Adding the Frontend
Deploy Next.js + Strapi blog on Kubernetes with Docker, Ingress routing, and service discovery. Complete tutorial for modern headless CMS deployment.

Shamaila Mahmood
July 29, 2025

Step 1: Why Next.js?
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:
- SEO Optimization: With static site generation (SSG) and server-side rendering (SSR), your blog content is indexable by search engines—critical for visibility.
- Fast Performance: Pages load quickly, especially when statically generated, improving both UX and SEO ranking.
- Flexible Rendering Options: Mix SSG and SSR depending on content type.
- Developer Experience: File-based routing, TypeScript support, and hot reloading make Next.js incredibly productive.
- API Routes: Backend logic (like preview endpoints) can live alongside your frontend.
For a blog, where search engine visibility and snappy content delivery matter, Next.js is the gold standard.
Understanding the Architecture
Before we dive into deployment, let's understand how our Next.js frontend will interact with Strapi:
- Content Fetching: Next.js will make HTTP requests to Strapi's REST API or GraphQL endpoint to fetch blog posts, author information, and media
- Static Generation: At build time, Next.js can pre-generate pages using Strapi content, resulting in lightning-fast page loads
- Dynamic Rendering: For real-time content or personalized experiences, Next.js can fetch data on each request
- Media Handling: Images and other media stored in Strapi will be referenced and optimized by Next.js
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.
Step 2: Building the Next.js Application
Creating the Frontend Application
First, let's understand what goes into a typical Next.js blog application that connects to Strapi:
Key Components:
- Pages: Blog listing, individual blog posts, author pages
- Components: Headers, footers, blog post cards, navigation
- Data Fetching: Functions to communicate with Strapi API
- Image Optimization: Next.js Image component for performance
Sample Next.js Project Structure
💡 Complete Source Code Available: You can find the complete, working Next.js frontend code in our GitHub repository: Github repo link
blog-frontend/
├── components/
│ ├── BlogCard.tsx # Blog post preview cards with modern styling
│ └── Layout.tsx # Site layout with sticky header and footer
├── lib/
│ └── strapi.ts # GraphQL client and API functions
├── pages/
│ ├── _app.tsx # Next.js app component
│ ├── index.tsx # Homepage with hero section and blog grid
│ └── blog/
│ └── [slug].tsx # Individual blog post pages
├── styles/
│ ├── globals.css # Tailwind CSS imports and custom styles
├── tailwind.config.js # Tailwind CSS configuration
├── postcss.config.js # PostCSS configuration
├── Dockerfile # Container configuration
├── next.config.js # Next.js configuration
├── package.json # Dependencies and scripts
└── tsconfig.json # TypeScript configuration
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 endpoint
Key Features:
- GraphQL client with Bearer token authentication
- TypeScript interfaces matching Strapi's schema structure
- Static site generation for optimal performance
💡 Complete Source Code Available: You can find the complete, working Next.js frontend code in our GitHub repository: Github repo link
The repository includes:
- Full Next.js application
- GraphQL integration with Strapi
- Docker configuration for containerization
- All necessary configuration files
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.
Building the Docker Image
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:
- Simplicity: Single Node.js process handles all requests, no need for nginx configuration
- Flexibility: Full Next.js features including API routes and middleware
- Developer Experience: Easier debugging and development alignment
- Modern Approach: Recommended by Vercel for containerized deployments
- Resource Efficiency: Optimized standalone output with minimal dependencies
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
Step 3: Deploying the Next.js Frontend
Now let's deploy the containerized Next.js application to Kubernetes. 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-time Configuration: The Strapi URL and token are set when building the Docker image using
--build-arg
- No Runtime Environment Variables: The running container doesn't need these variables since they're already compiled into the standalone server
- Port 3000: Next.js standalone server runs on port 3000
This 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.

Copy-Pasting sounds boring and outdated? Try our ready template
Start with the template that I have written specially to deploy Strapi/Next application in Kubernetes. Do not waste your time copy-pasting them.
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
Understanding Kubernetes Services
A Service in Kubernetes provides several critical functions:
- Service Discovery: Other pods can find your application using the service name (
blog-frontend
) - Load Balancing: If you scale to multiple pods, the service automatically distributes traffic
- Stable Networking: Even if pods are recreated, the service provides a consistent endpoint
- Port Abstraction: External clients don't need to know which port the container is actually listening on
In the service definition above:
selector
: Must match thematchLabels
used in the deploymentport
: The port that other services will use to connecttargetPort
: The actual port your application listens on inside the container
Apply 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.
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
.
What is 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.
Install NGINX Ingress Controller for Docker Desktop
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
Step 5: Configure Ingress for Public Access
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.
Understanding Ingress Path Routing
Kubernetes Ingress allows you to route HTTP/HTTPS traffic based on the request path:
- Path-based routing: Different URLs go to different services
- Host-based routing: Different domains go to different services
- Priority: More specific paths take precedence over general ones
In our case:
/admin
→ Routes to Strapi for content management/
→ Routes to Next.js frontend for the public blog
Updated Ingress Configuration
apiVersion: 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
Understanding Path Priority
The order of paths in the Ingress matters:
- Most specific first:
/admin
is more specific than/
- Catch-all last:
/
will match any path that doesn't match the above
This ensures that:
yourdomain.com/admin
→ Goes to Strapi adminyourdomain.com/
,yourdomain.com/blog/my-post
→ Goes to Next.js
Apply the updated Ingress:
kubectl apply -f blog-ingress.yaml
Step 6: Testing the Complete Setup
Verify Ingress Controller
First, make sure your Ingress controller is running and has an external IP:
kubectl get ingress blog-ingress kubectl get services -n ingress-nginx
Test Different Endpoints
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.

Conclusion
Congratulations! You've successfully built and deployed a complete blog platform with Next.js frontend and Strapi CMS backend running on Kubernetes. In this part, we accomplished several key milestones:
- Deployed a Next.js frontend that connects to your Strapi backend via GraphQL/REST APIs
- Configured Kubernetes Services for reliable internal communication between components
- Set up Ingress routing to expose both the public blog and admin interface through clean URLs
- Implemented path-based routing so
/admin
goes to Strapi, and/
displays your beautiful blog - Learned production deployment patterns including multi-stage Docker builds and service discovery
Your 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!