10 KiB
Publish the portfolio with Cloudflare and ROTLD
The primary address is https://lucapislaru.ro/. The accented domain lucapîslaru.ro redirects to it. Its ASCII/Punycode form is xn--lucapslaru-z8a.ro; these are two spellings of the same accented domain, not a third domain.
Use Cloudflare Workers Static Assets on the free plan. Keep both registrations and renewals at ROTLD. There is no application server, database, or request-time SSR to provision.
1. Export locally
Use Node.js 22.22.1 or a newer compatible Node 22 release. From the Portfolio project directory, run:
npm ci
npm run build:production
npm run verify:cloudflare
npm ci is only needed when setting up a fresh checkout or when dependencies change. It requires registry access. The current machine already has the dependencies.
The finished upload directory is dist-cloudflare/. The export command:
- checks the public content/asset references and prepared asset hashes;
- type-checks application source and Vite configuration using
tsconfig.production.json; - builds the client and build-time SSR code, then pre-renders the complete document and hydration snapshot;
- reads
config/site.production.jsonand creates an ignoredconfig/site.production.build.jsonwith a fresh production build ID; - verifies the exact artifact inventory, bytes, public content, metadata, and transfer budgets;
- scans the export for test hooks, fault/tier URL switches, source-map references, known private evidence fields, recognizable credential patterns, and local source paths;
- copies only the verified HTML, robots, sitemap, and referenced assets into the upload directory, and adds a real Cloudflare
_headersfile.
Production exports reject PORTFOLIO_TEST_*, VITE_* environment overrides and a non-production NODE_ENV. The export-specific Vite mode does not load local .env files or copy a public/ folder. Benchmark commands continue to use their existing separate build mode.
Only dist-cloudflare/ is configured for upload. Do not upload the repository, dist-client/, dist-ssr/, or the preview output. The generated _headers file is Cloudflare configuration, not a page served to visitors. It supplies security headers and one revalidation cache policy, avoiding stale media at stable filenames. Unknown URLs return 404; there is no catch-all homepage fallback.
The production config is public and committed; it contains no credentials. Its production-template build ID is replaced for each export. The preview config remains noindex and uses its existing localhost canonical URL.
2. Add both domains to Cloudflare
In Cloudflare, add each domain as a separate website/domain and select the Free plan:
lucapislaru.roxn--lucapslaru-z8a.ro(Cloudflare may displaylucapîslaru.ro)
Review the imported DNS records. Preserve any existing MX and email-related TXT records if you use domain email. Cloudflare will show an assigned pair of nameservers for each domain. Copy the exact pair for that domain; the pairs can differ. This is a DNS change, not a registrar transfer.
3. Set the nameservers at ROTLD
Open ROTLD and use Domenii .ro → Administrare Domenii → On-Line. Log in for the first domain and open Nameservers. Replace the old nameservers with the exact pair Cloudflare assigned to that domain, save, and complete any confirmation ROTLD requests. Repeat for the accented domain.
If DNSSEC is already enabled, remove/disable the old DNSSEC delegation at ROTLD before changing nameservers. Once Cloudflare reports the domain Active, enable DNSSEC in Cloudflare and publish its new DS details at ROTLD. Do not reuse the previous provider's DS record. Cloudflare nameserver/DNSSEC instructions, ROTLD administration guidance.
Wait until both domains show Active in Cloudflare before proceeding. No hosting IP address is needed for the main site.
4. Deploy the main site
Log in to Cloudflare's deployment tool from the project directory:
npx --yes wrangler@4.129.0 login
The browser asks you to authorize Wrangler. Credentials stay in Wrangler's local login storage, outside the site export. Then publish:
npm run deploy:cloudflare
This command publishes publicly. Wrangler first runs the entire production export again, so a failed build stops deployment. It creates/updates the Worker luca-pislaru-portfolio, uploads only dist-cloudflare/, and attaches lucapislaru.ro through the custom-domain entry in wrangler.jsonc.
Cloudflare manages the main site's DNS record and HTTPS certificate. If it reports a conflicting old website A/AAAA/CNAME record at the root, inspect that exact record and remove the obsolete website record before retrying; preserve email and other unrelated records. Do not create a placeholder A record for the main site's root.
The workers.dev address and version preview URLs are disabled in the configuration. This keeps alternate deployment URLs from exposing/indexing another copy of the site. Keep those settings disabled in the dashboard too. Workers custom domains.
To validate the deployment configuration without uploading anything:
npx --yes wrangler@4.129.0 deploy --config wrangler.jsonc --dry-run
5. Redirect the accented domain
Select the xn--lucapslaru-z8a.ro domain in Cloudflare. Under DNS → Records, create these redirect-only records, with the orange cloud/proxy enabled:
| Type | Name | Address | Proxy |
|---|---|---|---|
| A | @ | 192.0.2.1 | Proxied |
| A | www | 192.0.2.1 | Proxied |
192.0.2.1 is Cloudflare's documented placeholder for redirect-only hostnames; it is not your server's address. Cloudflare domain redirect instructions.
Open Rules → Overview → Create rule → Redirect Rule (also called Single Redirect). Name it Accented domain to primary. Use this custom filter expression:
(http.host eq "xn--lucapslaru-z8a.ro" or http.host eq "www.xn--lucapslaru-z8a.ro")
For the destination, select Dynamic and enter:
concat("https://lucapislaru.ro", http.request.uri.path)
Choose 308 Permanent Redirect, enable Preserve query string, and deploy the rule. It handles both HTTP and HTTPS without adding another copy of the website. Wait for the accented domain's Cloudflare edge certificate to become Active before checking HTTPS redirects.
6. Redirect www and HTTP on the main domain
Switch to the lucapislaru.ro domain. Add a proxied A record named www pointing to 192.0.2.1. Leave the root record managed by Workers.
Create a second Redirect Rule named Primary HTTPS canonical address with this filter:
(http.host eq "www.lucapislaru.ro" or (http.host eq "lucapislaru.ro" and not ssl))
Use the same dynamic destination expression, 308 status, and Preserve query string setting as above. This sends HTTP and www requests directly to the preferred HTTPS address. The HTTPS root must not match this rule, or it would loop. A separate HTTP-upgrade rule is not needed.
7. Check the live site and indexing
After HTTPS is active, verify:
| Address | Expected result |
|---|---|
| https://lucapislaru.ro/ | Portfolio loads with HTTP 200 |
| http://lucapislaru.ro/ | One 308 to https://lucapislaru.ro/ |
| https://www.lucapislaru.ro/ | One 308 to the primary domain |
| http://xn--lucapslaru-z8a.ro/ | One 308 to the primary domain |
| https://xn--lucapslaru-z8a.ro/ | One 308 to the primary domain |
| https://www.xn--lucapslaru-z8a.ro/ | One 308 to the primary domain |
| https://lucapislaru.ro/robots.txt | Allow crawling; sitemap names the main domain |
| https://lucapislaru.ro/sitemap.xml | Contains https://lucapislaru.ro/ |
| https://lucapislaru.ro/not-a-real-page | HTTP 404 |
| https://lucapislaru.ro/.env | HTTP 404 |
Check an accented-domain URL with a path and query, too: /not-a-real-page?check=1 should redirect to the identical path/query on the primary domain, which then correctly returns 404. Disable JavaScript in your browser and reload the homepage: the actual portfolio text and contact disclosure should remain usable. Re-enable JavaScript and check the animated experience.
View the homepage source and confirm index, follow, a canonical URL of https://lucapislaru.ro/, and social image/structured-data URLs on that same origin. Do not place a Cloudflare Access login or a blanket bot challenge in front of the public domain.
In Google Search Console, add a Domain property for lucapislaru.ro, verify it by adding Google's TXT record in Cloudflare DNS, and submit https://lucapislaru.ro/sitemap.xml. Inspect the homepage URL and request indexing. The site becomes eligible for indexing; Google decides whether and when to index it. Google sitemap guidance.
Later updates and verification scope
For future updates, run npm run deploy:cloudflare from the intended checkout. It rebuilds and validates before publishing. Avoid building a preview and production export concurrently because they share intermediate dist* directories.
This is a static Cloudflare deployment workflow. The existing Nginx signed-release/activation scripts are separate and are not executed by this command. The production type-check intentionally excludes test files; the general npm run typecheck and full test suite remain separate quality checks. The old smoke:production command assumes the Nginx signed-manifest header and a literal pre-rendered mailto: link, so it is not the Cloudflare acceptance command.
Export checks establish the exact public file boundary and detect known prohibited surfaces. They do not certify private source history, all possible secrets, or the semantic privacy of every screenshot. Existing public portfolio descriptions, approved screenshots, contact details and social links remain public. The encoded email is recoverable by visitors, as required for the contact feature. No credentials belong in public content or frontend source.