Migrating From Nuxt to Next.js With Google App Engine
Gradually migrate apps with microservices
This article will walk you through migrating from a Nuxt app to a Next.js using Google App Engine services. Here is the final code base on GitHub.
Dependencies
Here are the dependencies you’ll need:
- next@13.1.1
- nuxt@3.0.0
Overview
Suppose we have a monorepo app and structure a Nuxt app and a Next.js app in the repository.
./nuxt_to_next
├── package.json
├── packages
│ ├── next
│ └── nuxt
└── yarn.lock
The Nuxt project is the main application, and we will gradually migrate to the Next.js project on a page-by-page basis. The Nuxt application has four pages: Home, About, Works, and Blog:
We will migrate the About page from Nuxt to Next.js in this article.
Here is an overview of the implementation:
- Deploy Nuxt3 app
- Create an About page on Next.js
- Deploy Next.js app
- Override routing rules
Deploy Nuxt App
First, we will deploy the Nuxt app to the Google App Engine.
Let’s create a packages/nuxt/app.yaml
in the Nuxt project. Here’s the code:
env: standard
runtime: nodejs16
instance_class: F1
automatic_scaling:
max_instances: 1
max_idle_instances: 1
min_instances: 0
min_idle_instances: 0
handlers:
- url: /.*
script: auto
secure: always
env_variables:
NUXT_HOST: "0.0.0.0"
NUXT_PORT: "8080"
NODE_ENV: "production"
During the Node.js deployment on Google App Engine, the runtime installs the dependencies using the npm install
or yarn install
then run the server by npm start
or yarn start
. To make it work on Nuxt3, we will need to update some scripts in the package.json
like so:
{
"private": true,
"name": "package-nuxt",
"version": "1.0.0",
"scripts": {
- "build": "nuxt build",
+ "build": "yarn nuxt:prepare && nuxt build",
- "dev": "nuxt dev",
+ "dev": "yarn nuxt:prepare && PORT=3001 nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
+ "start": "HOST=0.0.0.0 PORT=8080 node .output/server/index.mjs",
- "postintall": "nuxi prepare",
+ "nuxt:prepare": "nuxi prepare",
},
}
Since Google App Engine installs the dependencies with NODE_ENV=production
, we don’t want the postintall
to be executed during the installation because the nuxi
will not be included in the dependencies. To start the server, run the output/server/index.mjs
which is generated by running the yarn build
.
Let’s build it using the following command:
NODE_ENV=production yarn build
Then deploy it with this command:
gcloud app deploy
Create an About Page on Next.js
Next, we will create an About page on the Next.js project. We will create each link using a
tag instead of NextLink
because the Next.js project doesn't have other pages so far, we need to navigate them to the Nuxt project.
Let’s create a packages/next/pages/about.tsx
. Here’s what that looks like:
import React from 'react'
export default function About() {
const title = 'About'
return (
<div>
<div className="text-sm font-medium text-center text-gray-500 border-b border-gray-200">
<ul className="flex flex-wrap -mb-px">
<li className="mr-2">
<a href="/" className="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">Home</a>
</li>
<li className="mr-2">
<a href="about" className="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">About</a>
</li>
<li className="mr-2">
<a href="/works" className="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">Works</a>
</li>
<li className="mr-2">
<a href="/blog" className="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">Blog</a>
</li>
</ul>
</div>
<div className="p-4">
<h1>Hello, {title} from Next.js</h1>
</div>
</div>
)
}
This page looks like this:
Deploy Next.js App
Next, let’s deploy the Next.js project.
Create a packages/next/app.yaml
, and add this code:
env: standard
runtime: nodejs16
instance_class: F1
automatic_scaling:
max_instances: 1
max_idle_instances: 1
min_instances: 0
min_idle_instances: 0
handlers:
- url: /_next/static
static_dir: .next/static
- url: /(.*\.(gif|png|jpg|ico|txt|svg))$
static_files: public/\1
upload: public/.*\.(gif|png|jpg|ico|txt|svg)$
- url: /.*
script: auto
secure: always
env_variables:
HOST: "0.0.0.0"
PORT: "8080"
NODE_ENV: "production"
Then add the service
property as follows:
env: standard
runtime: nodejs16
instance_class: F1
+ service: next
automatic_scaling:
max_instances: 1
max_idle_instances: 1
min_instances: 0
min_idle_instances: 0
handlers:
- url: /_next/static
static_dir: .next/static
- url: /(.*\.(gif|png|jpg|ico|txt|svg))$
static_files: public/\1
upload: public/.*\.(gif|png|jpg|ico|txt|svg)$
- url: /.*
script: auto
secure: always
env_variables:
HOST: "0.0.0.0"
PORT: "8080"
NODE_ENV: "production"
This will create a Service under the same application and an instance separated from the Nuxt project.
By using services, you can deploy multiple services that behave like microservices. Each app engine has to include at least one service. If you skip the service field in the app.yaml
, then the default
service is automatically applied. In this case, the Nuxt project is the default
service, and the Next.js project is the next
service.
Go to the next package and deploy it with this code:
cd packages/next
gcloud app deploy
Override Routing Rules
Now that the Next.js project can handle the About page, we will add a new routing rule to navigate to Next.js from Nuxt.
Go to the Nuxt project, and add packages/nuxt/dispatch.yaml
like this:
dispatch:
- url: "*/about"
service: next
- url: "*/_next/*"
service: next
The dispatch.yaml
allows you to override routing rules to send incoming requests to a specific service based on a path or hostname in the URL. In this case, all requests to the about page will be redirected to the Next.js project without changing the URL. The Next.js app needs to serve the static files under the /_next
folder.
Let’s deploy the dispatch file with this command:
gcloud app deploy dispatch.yaml
Once it's finished, we will change the link to the about page in the Nuxt project.
Open up a packages/nuxt/layouts/default.vue
and change the link as follows:
<template>
<div>
<div class="text-sm font-medium text-center text-gray-500 border-b border-gray-200">
<ul class="flex flex-wrap -mb-px">
<li class="mr-2">
<NuxtLink to="/" class="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">Home</NuxtLink>
</li>
<li class="mr-2">
- <NuxtLink to="/about" class="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">About</NuxtLink>
+ <a href="/about" class="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">About</a>
</li>
<li class="mr-2">
<NuxtLink to="/works" class="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">Works</NuxtLink>
</li>
<li class="mr-2">
<NuxtLink to="/blog" class="inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300">Blog</NuxtLink>
</li>
</ul>
</div>
<div class="p-4">
<slot />
</div>
</div>
</template>
Build it and deploy it.
NODE_ENV=production yarn build
gcloud app deploy
Once it’s deployed, it should work like so:
The About page is served through the Next.js project without changing the URL, while the other pages run in the Nuxt project.
Summary
Using services in Google App Engine allows you to share app engine features and communicate with another one through the dispatch file. Since the services behave like microservices, you can deploy multiple services independently and run them as a set of microservices.
In this article, we implemented Next.js from Nuxt, which can be applied to any framework, such as Remix and Astro. If you use Kubernetes on your microservices, you can achieve the same architecture using a service mesh like Istio.
Thanks for reading. Stay tuned for more.