Prepared by
AI&Media
For
Viron Portugal
Next.js 16 · GTM-NMK3LZ7B · vironportugal.com

The request was to install GTM. The real issue is that the site has no thank-you page.

We reviewed the source code your developer shared, along with the live site. Installing GTM and the domain verification is a ten minute job, and he is right that this is a custom-coded site rather than a website builder. But we found something that changes the order of the work: the contact form never changes the URL. Set up the standard way, by destination page, the account would record zero leads while appearing fully installed. Separately, the site takes 8.2 seconds to render on mobile. All three items are already fixed, compiled and verified in a real browser.

01What is in place today

The site is a single landing page built with Next.js 16 and React 19, bilingual English and Portuguese through a language toggle, hosted on Namecheap with LiteSpeed. The contact form sends email through Resend. The actual application code is eight files: the rest of the 377 MB archive was node_modules, .next and the .git folder.

Measurement layerStatusConsequence
Google Tag ManagerNot installedNo event leaves the site
Google Analytics 4Not installedNo behavioural baseline
Meta PixelNot installedCampaigns optimise without signal
Meta domain verificationNot installedNo control over the domain's ad rules
Thank-you pageDoes not existCovered in section 02
Meta descriptionMissingGoogle writes the search snippet on its own

The account starts from zero. That has an upside: the structure can be built correctly from day one, with no misconfiguration inherited from anyone.

02The issue that changes the order of the work

Without this resolved first, installing GTM produces the worst possible outcome: everything looks configured, the tag shows as active, and the report still shows zero leads.

What the code does
Submits without navigating

The form uses a Next.js Server Action. It sends the data in the background, receives confirmation and only swaps the button text to a success state. No reload, no /thank-you, the URL stays identical.

What analytics sees
Nothing happened

A conversion configured by destination URL, which is the default approach, never fires. Paid campaigns run without a conversion signal, and the algorithm optimises blind, spending to reach people who are never recognised as leads.

What it costs
Every euro spent is unattributed

Until a real conversion event exists, no channel can be compared to another and no budget decision is based on evidence. This is why it had to be fixed before any tag went live, rather than after.

→How it was fixed

The event now fires at the exact point where the server confirms the submission, inside handleSubmit, instead of relying on a URL change.

setBtnStatus(btnStatuses.success);

// Conversion: fires only after the server action confirms the email was sent.
track('generate_lead', {
  form_id: 'contact_main',
  form_language: lang,
  user_data: { email: data.email, phone: data.phone },
});

The user_data object is included deliberately. It is what allows Enhanced Conversions in Google and Advanced Matching in Meta to be switched on later, without needing a second round of development work.

The same gap applied to the quote buttons. All six "request a quote" CTAs only scroll the page down to the form, so none of them produced any signal either. They now fire quote_button_click, carrying the button's own text and its position on the page, which makes it possible to see which CTA actually brings the lead.

The WhatsApp link in the footer now fires its own event (whatsapp_click), since it is the third contact path on the site.

03Mobile speed

Measured with Google PageSpeed Insights on 7 August, mobile profile, slow 4G network.

63
Performance
89
Accessibility
100
Best practices
91
SEO
MetricValueReading
Largest Contentful Paint8.2 sMore than three times the 2.5 s threshold
Speed Index8.7 sContent takes too long to appear
First Contentful Paint2.6 sAcceptable
Total Blocking Time0 msNo JavaScript blocking
Cumulative Layout Shift0Stable layout

→The measured cause

JavaScript is not the problem: blocking time is zero. The weight is in the images.

On first load the site downloads six images in parallel, roughly 2 MB in total, all at the highest priority. Five of them sit below the fold and a visitor would only reach them after scrolling. On a slow 4G connection, which is the test scenario, that alone accounts for the 8.2 seconds. On top of that, the project photos are stored as PNG, a format designed for graphics rather than photography, and three of them exceed 400 KB each.

→What was changed

Initial mobile page loadBeforeAfter
Images downloaded1,962 KB126 KB
Requests116
Images at highest priority61
Total weight of image files3,488 KB1,097 KB
Server side, not code
The site's images have no caching at all

Files under /public are served with no Cache-Control header, so returning visitors re-download every image on every visit. The Next.js build files are already correct, only the public folder was left out. This is three lines in the LiteSpeed .htaccess, and the exact snippet is included in the package. Since the hosting runs cPanel, it can be edited directly in the panel's File Manager.

PageSpeed also flags a few accessibility items that were left untouched because they affect the visual design and are your call: buttons without an accessible name, insufficient colour contrast in some text, and heading levels out of sequence. They are recorded here for a second round.

04One item that needs action today

Security
A live Resend API key was included in the shared archive

The archive contained my-app/.env.local with an active RESEND_API_KEY, along with the .git folder and node_modules. That key can send email on behalf of info@vironportugal.com, which means anyone holding it can send mail that appears to come from your domain.

The project's own .gitignore already excludes .env files, so this was not intended to leave the development machine. The key should be rotated in the Resend dashboard. Nothing else needs to change: your sending setup itself is correct, with SPF, DKIM and DMARC properly configured, so email from the form is being delivered.

The node_modules and .git folders are also why the archive arrived at 377 MB for a project whose real code fits in eight files.

05The package for your developer

Rather than returning the full 377 MB, the package contains only what changed: three replaced files, ten new images, a line-by-line diff so he can review before applying, and a README in English.

viron-tracking-performance.zip 1.1 MB · 3 files changed · 10 images · README + diff
Download package
1
Copy the files over

app/layout.tsx, app/page.tsx, app/Components/Gallery.tsx and the ten .webp files into public/.

2
Build and deploy

npm run build, then deploy exactly as usual. No new dependency was added: GTM is loaded through next/script, which already ships with Next.js, so there is no need to run npm install.

3
Add image caching

Three lines in .htaccess. The exact snippet is in the README and can be applied through cPanel.

→Verified before delivery

The code was compiled from your own source and opened in a real headless browser at a mobile viewport. Each item below was confirmed on screen, not assumed.

The form test was run with email sending disabled, so that no false enquiry would land in your inbox. The file was then restored to its original state and checked byte for byte.

06What happens after the deploy

Once the site is published, the remaining work is on our side and no longer depends on your developer.

Shortcut available
The Meta verification can be done today, without the developer

Meta accepts a DNS TXT record as an alternative to the meta tag. Your domain, DNS and hosting are all with Namecheap, so the record can be added under Domain List, Manage, Advanced DNS. It takes a couple of minutes and requires no deploy. The meta tag is included in the package either way: both routes work and do not conflict.

Installing the tag was the request. Making the lead count is the work.

The package is ready, tested and verified against your own source. Once it is deployed, Viron's measurement setup starts correct from the very first euro invested, and every campaign decision after that is based on a number you can trust.

Message Juliana →
DIAGNOSTIC AIM.VRN.001
ISSUED 07/08/2026 · AI&MEDIA
BASED ON THE SOURCE CODE PROVIDED BY THE DEVELOPER AND THE LIVE SITE