Getting started with Capacitor using Next.js on iOS

We previously talked about how to get started with Next.js on Android. In that article though we had to skip over iOS setup as we didn’t have access to a Mac. This week I finally got my hands on a Mac mini, so I spent a couple days troubleshooting problems with Next.js and Capacitor on iOS.

How to use Next.js and Capacitor together on iOS

Before doing anything else, start setting up your app by following Capacitor’s iOS quick start guide. All the advice there is still applicable to Next.js.

Remove server-side functions from your app

If your app is already fully built and using Next-specific functionality such as getServerSideProps or getInitialProps, I have some bad news: these won’t work with Capacitor.

At this point you need to make a fairly important decision. Do you:

  • rework your app so that you don’t need them?
  • Or create a slightly different codebase to work with Capacitor?

Unfortunately that’s it for solutions as our final goal is to statically export our app into HTML and Javascript files.

Thankfully getStaticProps is still very much usable, as it is called during the export to compile static pages.

Fixing the Next.js Router bug in Capacitor for iOS

The biggest problem I’ve found while working on iOS is related to Next.js’ router functionality which I use extensively in my App.

As I’ve recently learned, Next.js has a function which checks the scheme of each link passed to the router (the scheme being the part before the domain: http and https).

Unfortunately, hardcoded in this function are the values that Next.js accepts as valid, which are limited to http and https. Since Capacitor uses a custom scheme capacitor:// this means that if you start your app and try to use the Router to navigate to any other page, the navigation will silently fail.

Unfortunately there’s not an easy way to fix this, but let’s go to some possible solutions.

Wait for the next release of Next.js

As unhelpful as this might sound, if you don’t want to use the workaround below, this might be your best option.

A fix to the problem that causes Router to break on iOS has actually been merged into the Next.js Canary branch. While this hasn’t been part of a release yet, I expect it will make its way into Next.js at some point in the near future.

I’ll make sure to keep the article updated and mention when this fix makes its way into Canary and eventually into a main release.

As of Next@10.0.0 the router issue still isn’t fixed

Patch the file yourself to solve the Next.js Router bug in Capacitor

This is the solution I ended up using myself, as I just couldn’t wait for the fix to be officially released. It sounds complex but it’s actually much simpler than you think.

We’ll be using the patch-package utility, which allows us to edit one of our dependencies, save it and then apply those changes every time we run npm install. The great thing about this is that the patch will be applied to anyone working on your project without any extra setup, ensuring the problem is fixed on all versions.

First of all, add a postinstall script to package.json:

 "scripts": {
   "postinstall": "patch-package"
 }

Then install the package:

npm i patch-package

Now we’ll have to go edit the file that’s causing the Routing to fail inside your Next.js dependency inside node_modules. The file is located at /node_modules/next/dist/next_server/lib/router/utils/parse-relative-urls.js.

Now, you may notice this file has been compiled and minified. Thankfully it’s also quite short and straight forward, so we can easily still edit it. You could very well pull the Next.js repository yourself, change the source and then build it yourself. However, since this is such a simple edit I suggest you don’t bother with that.

Open up your editor of choice’s search functionality and look for the string protocol!=='http:'&&protocol!=='https:'. You’ll want to change that to protocol!=='http:'&&protocol!=='https:'&&protocol!=='capacitor:' to allow capacitor URLs to be parsed by the router.

Once you’re done, close the file and in your terminal run:

npx patch-package next

This will run the patch-package command and create a /patches directory.

If you want to test it just delete your node_modules folder and run npm install once more. You should see a message like this:

> patch-package

patch-package 6.2.2
Applying patches...
next@9.5.5 ✔

Go check the file you previously edited. The changes you made should have gone through!

Start up your App in XCode, I can almost assure you everything should now be working.

Dealing with iPhone notches and Webview

Unlike Android, where using the space behind the notch is a simple setting on Capacitor’s side, on iPhones that space is always utilised and your app will be stretched behind it. So, naturally, you’ll need to work around that to ensure that your content isn’t hidden or covered.

Interestingly enough Apple implemented this by adding env CSS variables to their Safari browser, and since Capacitor uses Safari on iPhone, it’s relatively painless to fix.

Apple has a pretty good guide on working with the safe area in the browser, and in general you probably won’t need to do much more than apply the safe area values to your body to make it fit the page.

In my case however, I was using a lot of height: 100vh, which unfortunately is not affected by paddings added to the body.

To fix this I created a couple of CSS variables:

:root {
  // Safe area contains how much space I should leave from the top and returns 0 if it's not supported
  --safe-area-inset-top: env(safe-area-inset-top);
  // A replacement for 100vh
  --screen-height: calc(100vh - var(--safe-area-inset-top));
}

The way it works is fairly simple. Anywhere I need to use 100vh to define a full-page element, I just use var(--screen-height) instead. It will equal 100vh on devices that don’t support the safe area, but display correctly on the ones that do!

Dealing with iOS zooming in when selecting inputs

If you’ve worked with Capacitor on iOS and tested out your app just a little bit, it’s likely you’ve noticed that iOS zooms the screen when you select inputs that require typing.

This is actually a really easy fix if you understand the cause: Apple defaults this behaviour because of their strict accessibility guidelines. The zoom on input focus on iOS is caused by the font-size property of your inputs, if it’s set to anything lower than 16px, iOS will automatically zoom in the screen.

There are a lot of deprecated workarounds floating on Google as they’ve all been slowly fixed in recent iOS releases. The easiest fix to this is to set your inputs to:

input {
  font-size: 16px;
}

And there you have it. If you have any questions, simply comment below.

Also, make sure you also check out our how to get started with Capacitor and Next.js on Android if you haven’t already!

0