Deploy a Permanent dApp
Deploy a permanent web application to Arweave with a human-readable ArNS name — using only your Solana wallet. Your app will be served by the network of decentralized gateways with zero ongoing hosting costs.
What You'll Build
By the end of this guide, your app will be:
- Permanently stored on Arweave (can never be deleted or modified)
- Accessible at
https://yourname.ar.io(and every other ar.io gateway) - Owned by you as a Metaplex Core NFT (the ArNS name token)
Prerequisites
- Node.js 18+
- A Solana wallet with SOL and ARIO tokens
- A built web app (any framework that outputs a static folder — React, Next.js, Vue, Svelte, etc.)
npm install @ar.io/sdk @ardrive/turbo-sdk @solana/kit bs58Requires @ar.io/sdk version 3.23+ for Solana support.
Step-by-Step
Build Your App
Generate a static build of your web application:
# next.config.js must have: output: 'export'
npm run build
# Output: ./out/npm run build
# Output: ./dist/# Build your app to a static folder
npm run build
# Use whatever output directory your framework createsSet Up Your Signer
Create a shared setup file that both Turbo (for uploads) and the ar.io SDK (for naming) can use:
// setup.ts
import { ARIO, ANT } from '@ar.io/sdk';
import { TurboFactory } from '@ardrive/turbo-sdk';
import { createKeyPairSignerFromBytes } from '@solana/kit';
import bs58 from 'bs58';
import fs from 'fs';
// Load your Solana keypair
const keypairBytes = new Uint8Array(
JSON.parse(fs.readFileSync('./solana-keypair.json', 'utf-8')),
);
// For ar.io SDK (ArNS names, records)
export const signer = await createKeyPairSignerFromBytes(keypairBytes);
export const ario = ARIO.mainnet({ signer });
// For Turbo (file uploads to Arweave)
// Turbo uses its own signer format — pass the secret key as base58
export const turbo = TurboFactory.authenticated({
privateKey: bs58.encode(keypairBytes.slice(0, 32)),
token: 'solana',
});Security: Never commit your keypair file to version control. Use a dedicated deployment wallet with only the SOL and ARIO needed for the operation.
Upload to Arweave via Turbo
Upload your build folder. Turbo bundles all files into an Arweave manifest — a single transaction ID that maps to all your app's files:
// deploy.ts
import { turbo, ario, signer } from './setup';
import { ANT } from '@ar.io/sdk';
const ARNS_NAME = 'my-cool-app'; // the ArNS name you want
const BUILD_DIR = './dist'; // your build output folder
// Step 1: Upload the build folder
console.log('Uploading to Arweave...');
const uploadResult = await turbo.uploadFolder({
folderPath: BUILD_DIR,
dataItemOpts: {
tags: [
{ name: 'App-Name', value: ARNS_NAME },
{ name: 'App-Version', value: '1.0.0' },
],
},
});
const manifestTxId = uploadResult.manifestResponse.id;
console.log(`Uploaded! Manifest TX: ${manifestTxId}`);
console.log(`Direct access: https://turbo-gateway.com/${manifestTxId}`);Files under 100KB are free to upload via Turbo. Larger uploads are paid with SOL from your wallet — no pre-funding needed when using just-in-time payments.
Register an ArNS Name (If You Don't Have One)
Skip this step if you already own the ArNS name.
// Check if the name is available
try {
const existing = await ario.getArNSRecord({ name: ARNS_NAME });
console.log(`Name "${ARNS_NAME}" is already registered`);
} catch {
// Name is available — register it
console.log(`Registering "${ARNS_NAME}"...`);
// Check the cost first
const cost = await ario.getTokenCost({
intent: 'Buy-Name',
name: ARNS_NAME,
type: 'lease',
years: 1,
});
console.log(`Cost: ${cost / 1_000_000} ARIO`);
// Buy it (this mints an ANT as a Metaplex Core NFT)
await ario.buyRecord({
name: ARNS_NAME,
type: 'lease',
years: 1,
});
console.log(`Registered "${ARNS_NAME}"!`);
}Point Your Name to Your App
Set the ANT's root (@) record to your uploaded manifest:
// Get the ANT mint address from the ArNS record
const record = await ario.getArNSRecord({ name: ARNS_NAME });
// Initialize the ANT
const ant = ANT.init({ signer, processId: record.processId });
// Set the root record to your manifest
console.log('Setting ArNS record...');
await ant.setRecord({
undername: '@',
transactionId: manifestTxId,
ttlSeconds: 3600,
});
console.log('Done! Your app is live at:');
console.log(` https://${ARNS_NAME}.ar.io`);
console.log(` https://${ARNS_NAME}.turbo-gateway.com`);Verify
Wait a minute for gateways to pick up the new record, then verify:
curl -I https://my-cool-app.ar.io
# Should return 200 OK with your app's index.htmlYour app is now permanently hosted and accessible through every ar.io gateway in the network.
Updating Your App
Since Arweave data is immutable, "updating" means uploading a new version and updating your ArNS record to point to it:
// Upload new version
const newUpload = await turbo.uploadFolder({
folderPath: './dist',
dataItemOpts: {
tags: [
{ name: 'App-Name', value: 'my-cool-app' },
{ name: 'App-Version', value: '2.0.0' },
],
},
});
// Update the record
const ant = ANT.init({ signer, processId: record.processId });
await ant.setRecord({
undername: '@',
transactionId: newUpload.manifestResponse.id,
ttlSeconds: 3600,
});
// Old version is still on Arweave forever — instant rollback if neededUsing Undernames for Staging
You can use undernames to deploy staging environments alongside production:
// Deploy staging version
await ant.setRecord({
undername: 'staging',
transactionId: stagingManifestTxId,
ttlSeconds: 300, // short TTL for faster updates
});
// Access at: https://staging_my-cool-app.ar.io
// When ready, promote to production
await ant.setRecord({
undername: '@',
transactionId: stagingManifestTxId,
ttlSeconds: 3600,
});Full Script
Here's the complete deployment script you can adapt:
import { ARIO, ANT } from '@ar.io/sdk';
import { TurboFactory } from '@ardrive/turbo-sdk';
import { createKeyPairSignerFromBytes } from '@solana/kit';
import bs58 from 'bs58';
import fs from 'fs';
const ARNS_NAME = process.argv[2] || 'my-app';
const BUILD_DIR = process.argv[3] || './dist';
async function deploy() {
// Load Solana keypair
const keypairBytes = new Uint8Array(
JSON.parse(fs.readFileSync('./solana-keypair.json', 'utf-8')),
);
// Init SDKs
const signer = await createKeyPairSignerFromBytes(keypairBytes);
const ario = ARIO.mainnet({ signer });
const turbo = TurboFactory.authenticated({
privateKey: bs58.encode(keypairBytes.slice(0, 32)),
token: 'solana',
});
// Upload
console.log(`Uploading ${BUILD_DIR}...`);
const upload = await turbo.uploadFolder({
folderPath: BUILD_DIR,
dataItemOpts: {
tags: [{ name: 'App-Name', value: ARNS_NAME }],
},
});
const txId = upload.manifestResponse.id;
console.log(`Uploaded: https://turbo-gateway.com/${txId}`);
// Get ANT and update record
const arnsRecord = await ario.getArNSRecord({ name: ARNS_NAME });
const ant = ANT.init({ signer, processId: arnsRecord.processId });
console.log('Updating ArNS record...');
await ant.setRecord({
undername: '@',
transactionId: txId,
ttlSeconds: 3600,
});
console.log(`\nLive at: https://${ARNS_NAME}.ar.io`);
}
deploy().catch(console.error);# Usage
npx tsx deploy.ts my-cool-app ./distNext Steps
ArNS Name Management
Manage records, undernames, and transfer ownership
Version Control with Undernames
Use undernames for staging, rollbacks, and multi-version deployments
Turbo Upload Options
Advanced upload features — encryption, tagging, folder uploads
How is this guide?